Class文件的字段与方法

2021-03-02  本文已影响0人  你怕是很皮哦

引导

Class文件的基本结构
Class文件的常量池
Class文件的访问标志,类索引,父类索引,接口索引
Class文件的字段和方法

Class文件中的字段

Class文件的基本结构 一文中,简单的介绍了 class 文件中的字段,它是由字段计数器(field_count)和字段表(fields)两个部分组成,排列在接口表(interfaces)的后面,如下图红色框所示。

image.png

你知道在 java 中字段由哪几个部分组成的吗?这里我绘制了 java 中字段的组成图,如下所示。

image.png
  1. 访问权限即 privateprotectedpublic和包访问权限;
  2. static 表示是否是静态;
  3. final 表示是否可被修改;
  4. volatile 即并发可见性;
  5. transient 即是否序列化;

JVM 规定了用 Field_info 结构体来描述字段,它的结构如下图所示。

image.png
  1. access_flags 为字段的访问标志,比如前面所说的访问权限,是否静态等都会被 java 编译器编译成的 access_flags
  2. name_indexdescriptor_index 是一个指向常量池(constant_pool)数组的有效索引值;
  3. attributes_count 描述了字段的属性个数;
  4. attributes 是一个由 ConstantValue 结构体组成的数组,数组长度为 attributes_count

字段的访问标志(access_flags)占用 2 个字节,每一位表示的信息如下图所示。

image.png

举个栗子。定义如下代码。

public class ClassFile {
    
    private static final transient String test = "hello";
}

按照上面的描述,test 字段在 class 文件中的访问标志(access_flags)应该为 ACC_PRIVATEACC_STATICACC_FINALACC_TRANSIENT。其编译成的 class 文件如下图所示,和猜想一致。

image.png

Class文件中的方法

Class文件的基本结构 一文中,简单的介绍了 class 文件中的方法,它是由方法计数器(method_count)和方法表(methods)两个部分组成,排列在字段表(fields)的后面,如下图红色框所示。

image.png

你知道在 java 中方法由哪几个部分组成的吗?这里我绘制了 java 中方法的组成图,如下所示。

image.png
  1. 访问权限即 privateprotectedpublic和包访问权限;
  2. static 表示是否是静态;
  3. final 表示是否可被修改;
  4. synchronized 是否是同步方法;
  5. native 是否是 native 方法;
  6. abstract 是否是抽象方法;
  7. 方法描述包括方法参数和方法返回值类型;

JVM 规定了用 Method_info 结构体来描述方法,它的结构如下图所示。

image.png

访问标志

访问标志(access_flags)占用 2 个字节,每一位表示的信息如下图所示。

image.png

举个栗子。定义如下代码。

public class ClassFile {
    
    public static synchronized final void test() {
    }
}

按照上面所讲,test() 在 class 文件中存储的访问标志(access_flags) 应该是 ACC_PUBLICACC_STATICACC_SYNCHRONIZEDACC_FINAL。下图为 ClassFile 生成的 class 文件,和我们的猜想一致。

image.png

名称索引

name_index 是一个指向常量池(constant_pool)数组的有效索引值,且此索引处的常量池项是 CONSTANT_Utf8_info 结构,描述了方法的名称。

描述符索引

descriptor_index 是一个指向常量池(constant_pool) 数组的有效索引值,且此索引处的常量池项是 CONSTANT_Utf8_info 结构,描述了方法的返回值类型和参数类型。

方法描述符 = (方法参数类型描述符列表)方法返回值类型描述符

举个栗子。定义如下代码。

public class ClassFile {
    
    private String name;
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
}

根据上面所说,我们猜想如下:

  1. getName() 方法在 class 文件中对应的方法描述符为 ()Ljava/lang/String;
  2. setName() 方法在 class 文件中对应的方法描述符为 (Ljava/lang/String;)V

ClassFile 对应的 class 文件如下图所示,和我们的猜想一样。

image.png

属性

属性记录了方法的一些属性信息。这些信息包括:

  1. 方法的代码实现,即机器指令;
  2. 方法声明的异常信息;
  3. 方法是否被标记为过时 @deprecated;
  4. 方法是否是编译器自动生成。

attribute_info 的结构如下图所示。

image.png

attribute_name_index 是一个指向常量池(constant_pool)数组的有效索引值,表示该 attribute_info 项表示的是哪一种类型的属性。属性名及其具体含义如下图所示。

image.png

attribute_length 表示属性值用多少个字节来进行存放;
attribute_value 表示存放的属性信息。

Code类型的属性

Code 类型的属性是方法最为重要的属性部分。因为它包含了 JVM 运行的机器码指令。

Code 属性一般由如下 4 个部分组成:

  1. 机器指令(Code);
  2. 异常处理跳转信息(ExceptionTable),如果代码中出现了 try{}catch{} 块,那么 try{} 块内的机器指令的地址范围记录下来,并且记录对应的 catch{} 块中的起始机器指令地址,当运行时在 try{} 块中有异常抛出的话,JVM 会将 `catch{} 块对应的起始机器指令地址传递给PC寄存器,从而实现指令跳转;
  3. java 源文件行号和机器指令的对应关系(LineNumberTable);
  4. 局部变量描述信息(LocalVariableTable),它会记录栈帧局部变量表中的变量和 java 源文件中定义的变量之间的关系,这个信息不是运行时必须的属性,默认情况下不会生成到 class 文件中。

局部变量描述信息有什么作用?我们在开发的时候经常会用到代码自动补全功能,而有的时候会发现调用方法的参数都是 p0,p1 这种没有实际意义的符号,这其实就是没有局部变量描述信息所导致的。

Code 属性的基本结构如下图所示。

image.png
  1. attribute_name_index 是一个指向常量池(constant_pool)数组的一个有效索引值,且常量池(constant_pool)数组在此索引出的项为代表 CodeConstant_Utf8_info 结构;
  2. attribute_length 表示属性值占用的长度;
  3. max_stack 表示操作数栈深度的最大值,在方法执行的任意时刻,操作数栈都不应该超过这个值,JVM 的运行的时候,会根据这个值来设置该方法对应的栈帧(Stack Frame)中的操作数栈的深度;
  4. max_locals 表示最大局部变量数,表示局部变量表所需要的存储空间大小;
  5. code_length 表示机器指令长度,即跟在其后的多少个字节是机器指令;
  6. code 机器指令区域。JVM 最底层的要执行的机器指令就存储在这里;
  7. exception_table_length 显式异常表长度,如果在方法代码中出现了 try{} catch() 形式的结构,该值不会为空,紧跟其后会跟着若干个 exception_table 结构体,以表示异常捕获情况;
  8. exception_table 显式异常表,start_pcend_pchandler_pc 中的值都表示的是PC计数器中的指令地址。exception_table 表示的意思是:如果字节码从第 start_pc 行到第 end_pc 行之间出现了 catch_type 所描述的异常类型,那么将跳转到 handler_pc 行继续处理;
  9. attribute_count 属性计数器,表示 Code 属性表的其他属性的数目;
  10. attribute_info 表示 Code 属性具有的属性,它主要分为三个类型的属性表:LineNumberTable 类型、 LocalVariableTable 类型。
    LineNumberTable 记录着 java 源文件和机器指令之间的对应关系;
    LocalVariableTable 记录着局部变量描述。

举个栗子。定义如下代码。

public class ClassFile {
    
    public void test() {
        String test = "hello";
        try {
            byte[] bytes = test.getBytes("utf-8");
        } catch(Exception e) {
        }
    }
}

它对应的 class 文件中的 Code 属性如下图所示。

image.png
上一篇下一篇

猜你喜欢

热点阅读