美文网首页
Class文件的字段与方法

Class文件的字段与方法

作者: 你怕是很皮哦 | 来源:发表于2021-03-02 16:25 被阅读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

    相关文章

      网友评论

          本文标题:Class文件的字段与方法

          本文链接:https://www.haomeiwen.com/subject/qdkxqltx.html