字节跳动青训营bitdance tg code属性详解

247 阅读6分钟

[这是我参与「第四届青训营 」笔记创作活动的第1天]

Code属性详解

关于Code属性,一般都是在method_info的attribute_info里面存有 。

Code属性分析

code属性是方法的一个最重要的属性。 因为它里面存放的是方法的字节码指令, 除此之外还存放了和操作数栈,局部变量相关的信息。 所有不是抽象的方法, 都必须在method_info中的attributes中有一个Code属性。下面是Code属性的结构, 为了更直观的展示Code属性和method_info的包含关系, 下面为method_info的结构描述:

下面依次介绍code属性中的各个部分。

  • attribute_name_index指向常量池中的一个CONSTANT_Utf8_info , 这个CONSTANT_Utf8_info 中存放的是当前属性的名字 “Code” 。

  • attribute_length给出了当前Code属性的长度(不包括前六字节)。

  • max_stack 指定当前方法被执行引擎执行的时候, 在栈帧中需要分配的操作数栈的大小。

  • max_locals指定当前方法被执行引擎执行的时候, 在栈帧中需要分配的局部表量表的大小。注意, 这个数字并不是局部变量的个数, 因为根据局部变量的作用域不同, 在执行到一个局部变量以外时, 下一个局部变量可以重用上一个局部变量的空间(每个局部变量在局部变量表中占用一个或两个Slot)。 方法中的局部变量包括方法的参数, 方法的默认参数this, 方法体中定义的变量, catch语句中的异常对象。

  • code_length指定该方法的字节码的长度, class文件中每条字节码占一个字节。

  • code存放字节码指令本身, 它的长度是code_length个字节。

  • exception_table_length 指定异常表的大小

  • exception_table就是所谓的异常表, 它是对方法体中try-catch_finally的描述。 exception_table可以看做是一个数组, 每个数组项是一个exception_info结构, 一般来说每个catch块对应一个exception_info,编译器也可能会为当前方法生成一些exception_info。 exception_info的结构如下(为了直观的显示exception_info, exception_table和Code属性的关系, Code属性更清楚各个数据项之间的位置关系和包含关系):

    下面具体了解一下exception_info的各个字段意思。

    • start_pc是从字节码(Code属性中的code部分)起始处到当前异常处理器起始处的偏移量。
    • end_pc是从字节码起始处到当前异常处理器末尾的偏移量。
    • handler_pc是指当前异常处理器用来处理异常(即catch块)的第一条指令相对于字节码开始处的偏移量。
    • catch_type是一个常量池索引,指向常量池中国的一个CONSTANT_Class_info数据项,该数据项描述了catch块中国的异常的类型信息。这个类型必须是java.lang.Throwable的或其子类。

    所以总的来说,一个异常处理器的意思是:如果偏移量从start_pc到end_pc之间的字节码出现了catch_type描述类型的异常,那么就跳转到偏移量为handler_pc的字节码去执行。如果catch_type为0,就代表不引用任何常量池项,那么这个exception_info用于实现finally子句。

  • attribute_count表示当前Code属性中存在的其他属性的个数。

  • attributes里存放了Code属性中的其他属性。Code属性中可以出现的其他属性有LineNumber和LocalVariableTable。

    • LineNumberTable属性

      LineNumberTable属性存在于Code属性中, 它建立了字节码偏移量到源代码行号之间的联系。 这个属性是可选的, 编译器可以选择不生成该属性。下面是该属性的结构:

      每个LineNumberTable中的line_number_table部分, 可以看做是一个数组, 数组的每项是一个line_number_info , 每个line_number_info 结构描述了一条字节码和源码行号的对应关系。 其中start_pc是这个line_number_info 描述的字节码指令的偏移量, line_number是这个line_number_info 描述的字节码指令对应的源码中的行号。可以看出, 方法中的每条字节码都对应一个line_number_info , 这些line_number_info 中的line_number可以指向相同的行号, 因为一行源码可以编译出多条字节码。

    • LocalVariableTable属性

      LocalVariableTable 属性建立了方法中的局部变量与源代码中的局部变量之间的对应关系。 这个属性存在于Code属性中。 这个属性是可选的, 编译器可以选择不生成这个属性。该属性的结构如下:

    • LocalVariableTypeTable属性

      JDK1.5以后新增的属性,仅仅是把记录字段描述符的descriptor_index替换成了字段的特征签名(Signature),对于非泛型类型来说,描述符和特征签名能描述的信息十几本一致的,但是泛型引入后,由于描述符中泛型的参数化类型被擦除了,所以需要使用这个属性代替descriptor_index。

    • Signature属性

      JDK1.5后新增的类型,用于记录泛型签名信息。

  • Exceptions属性

    首先需要说明, Exceptions属性不是存在于Code属性中的, 它存在于method_info中的attributes中。 和Code属性是平级的。 这个属性描述的是方法声明的可能会抛出的异常, 也就是方法定义后面的throws声明的异常列表, 请不要和上面提到的异常处理器混淆。 异常处理器描述了方法的字节码如何处理异常, 而Exceptions属性描述方法可能会抛出哪些以异常。下面是Exceptions属性的结构:

    • attribute_name_index和attribute_length就不多说了, 和其他属性是一样的
    • number_of_exceptions是该方法要抛出的异常的个数
    • exceptions_index_table可以看做一个数组, 这个数组中的每一项占两个字节, 这两个字节是对常量池的索引, 它指向一个常量池中的CONSTANT_Class_info。 这个CONSTANT_Class_info描述了一个被抛出的异常的类型。

总结,下面给出一张全局图用于整个对Code属性的了解:

实例分析

下面以一个实例进行验证, 实例代码:

import java.util.*;
​
public class TestClass {
    
    private static final List<String> list = new ArrayList<>();
    
    public static int test(List<String> s) throws Exception {
        int localVar = 0;
        try {
            localVar = 1;
            return localVar;
        } catch (Exception e) {
            localVar = 2;
            return 
​
localVar;
        } finally {
            localVar = 3;
        }
    } 
}

经过javap反编译后的test()方法部分:

public static int test(java.util.List<java.lang.String>) throws java.lang.Exception;
    descriptor: (Ljava/util/List;)I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=5, args_size=1
         0: iconst_0
         1: istore_1
         2: iconst_1
         3: istore_1
         4: iload_1
         5: istore_2
         6: iconst_3
         7: istore_1
         8: iload_2
         9: ireturn
        10: astore_2
        11: iconst_2
        12: istore_1
        13: iload_1
        14: istore_3
        15: iconst_3
        16: istore_1
        17: iload_3
        18: ireturn
        19: astore        4
        21: iconst_3
        22: istore_1
        23: aload         4
        25: athrow
      Exception table:
         from    to  target type
             2     6    10   Class java/lang/Exception
             2     6    19   any
            10    15    19   any
            19    21    19   any
      LineNumberTable:
        line 8: 0
        line 10: 2
        line 11: 4
        line 18: 6
        line 11: 8
        line 12: 10
        line 13: 11
        line 14: 13
        line 18: 15
        line 14: 17
        line 18: 19
        line 19: 23
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           11       8     2     e   Ljava/lang/Exception;
            0      26     0     s   Ljava/util/List;
            2      24     1 localVar   I
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
            0      26     0     s   Ljava/util/List<Ljava/lang/String;>;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 10
          locals = [ class java/util/List, int ]
          stack = [ class java/lang/Exception ]
        frame_type = 72 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]
    Exceptions:
      throws java.lang.Exception
    Signature: #32                          // (Ljava/util/List<Ljava/lang/String;>;)I

上面反编译的结果结合上面对Code属性的分析,就可以更加深入的了解Code属性了。