JVM学习笔记9-Java字节码文件结构剖析

    xiaoxiao2023-06-10  161

    java字节码结构

    Class字节码中有两种数据类型

    字节数据直接量—基本的数据类型

    u1—代表连续的1个字节组成的整体数据u2—代表连续的2个字节组成的整体数据u4—代表连续的4个字节组成的整体数据u8—代表连续的8个字节组成的整体数据

    表(数组) 是由多个基本数据或其他表,按照既定顺序组成的大的数据集合 表是有结构的—体现在:组成表的成分所在的位置和顺序都是已经严格定义好的

           使用javap -verbose命令分析一个字节码文件时,将会分析该字节码文件的魔数,版本号,常量池,类信息,类的构造方法,类中的方法信息,类变量与成员变量信息

    长度类型描述数量备注u4Magic魔数值为CAFEBABE16进制1Java创始人James Gosling制定u2minor_version次版本号1u2major_version主版本号1u2constant_pool_count常量池个数1cp_infoconstant_pool常量池表constant_pool_count-1u2access_flags类的访问控制权限1u2this_class类名1u2super_class父类名1u2interfaces_count接口个数1u2interfaces接口名interfaces_countu2fields_count域个数field_infofields域的表fields_countu2methods_count方法个数1method_infomethods方法表methods_countu2attributes_count附加属性个数1attribute_infoattributes附加属性的表attributes_count

    完整的Java字节码结构

    ClassFile{ u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count - 1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; fields_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }

    1 魔数

    所有.class文件的字节码文件的前4个字节都是魔数,魔数值为固定值—0xCAFEBABE

    2 版本信息

    分为主版本号和次版本号 以java version "1.8.0_131"为例

    1.8表示主版本号0表示次版本号131表示更新号

    主版本号对应的字节码信息

    主版本号字节码16进制1.8521.7511.6501.5491.4481.3471.246

    3 常量池

    一个Java类中定义的很多信息都是有常量池类维护和描述的,占据字节码文件内容的很大一部分可以将常量池看做是Class文件的资源仓库.比如Java类中定义的方法与变量信息都是存在在常量池中常量池中主要存储主要存储两类常量: 字面量 文本字符串Java中声明为final的常量值等 符号引用 类和接口的全局限定名字段的名称和描述符方法的名称和描述符

    3.1 常量池的总体结构

    Java类所对应的常量池主要由常量池数量与常量池组这两部分构成

    常量池数量紧跟在主版本数量之后,占据两个字节常量池组则紧跟在常量池数据之后 常量池数组中不同元素的类型,结构都是不同的,长度自然也不同每种元素的第一个数据都是U1类型—该字节是个标志位,占据一个字节JVM在解析常量池时,会根据U1类型来获取元素的数据结构

    值得注意的是,常量池数组中元素的个数=常量池数-1(其中0暂时不使用)

    目的是满足某些常量池索引值的数据在特定情况下需要表达不引用任何一个常量池的含义根本原因为索引为0也是一个常量,只不过它不位于常量表中,这个常量就对应null值,所以常量池的索引从1而非0开始

    在JVM规范中,每个变量/字段都有描述信息

    描述信息主要的作用是描述字段的数据类型,方法的参数列表(包括数量,类型与顺序)和返回值

    3.2 描述符规则

    3.2.1 基本数据类型和对象类型描述符规则

    根据描述符规则

    基本数据类型和代表无返回值的void类型都用一个大写字母来表示对象类型使用字符L+对象的全限定名称来表示

    为了压缩字节码文件的体积,对于基本数据,JVM都只使用一个大写字母来表示,如下所示:

    B—byteC—charD—doubleF— floatI—intJ—longS—shortZ—booleanV—voidL—对象类型,如Ljava/lang/String

    3.2.2 数组类型的描述符规则

    对于数组类型来说,每个维度使用一个前置的[来表示,如

    int[]被记录为[iString[][]被记录为[[Ljava/lang/String

    3.2.3 方法的描述符规则

    用描述符描述方法时,按照先参数列表,后返回值的顺序来描述

    参数列表按照参数的严格顺序放在一组()之内 如方法 String getUserInfoByIdAndName(int id, String name){}

    其描述符为 (I,Ljava/lang/String) Ljava/lang/String

    表1. Class文件结构中常量池中11中数据类型的结构总表 常量项目类型描述CONSTANT_utf8_infotagU1值为1lengthU2UTF-8编码的字符串长度bytesU1长度为length的UTF-8编码的字符串CONSTANT_integer_infotagU1值为3bytesU4按照高位在前存储的int值CONSTANT_float_infotagU1值为4bytesU4按照高位在前存储的float值CONSTANT_long_infotagU1值为5bytesU8按照高位在前存储的long值CONSTANT_Double_infotagU1值为6bytesU8按照高位在前存储的long值CONSTANT_class_infotagU1值为7indexU2指向全限定名常量项的索引CONSTANT_String_infotagU1值为8indexU2指向字符串字面量的索引CONSTANT_Fieldref_infotagU1值为9indexU2指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项indexU2指向字段描述符CONSTANT_NameAndType_info的索引项CONSTANT_Methodref_infotagU1值为10indexU2指向声明方法的类描述符CONSTANT_Class_info的索引项indexU2指向名称及类型描述符CONSTANT_NameAndType_info的索引项CONSTANT_Methodref_infotagU1值为11indexU2指向声明方法的接口描述符CONSTANT_Class_info的索引项indexU2指向名称及类型描述符CONSTANT_NameAndType_info的索引项CONSTANT_NameAndType_infotagU1值为12indexU2指向该字段或方法名称常量项的索引indexU2指向该字段或方法描述符常量项的索引

    表2中描述了11中数据类型的结构,其实在JDK1.7之后又新增了三种

    CONSTANT_MethodHandle_infoCONSTANT_MethodTypeInfoCONSTANT_InvokeDynamic_info 所以一共是14种

    4 访问标志

    长度为2个字节,访问标志信息包括

    该Class文件是类还是接口是否被定义为public是否是abstract如是类是否被声明成final

    表3. Class access and property modifiers–类访问和属性修饰符

    标志名值说明ACC_PUBLIC0x0001声明为public,可以从其包外部访问ACC_PRIVATE0x0002声明为private,仅共当前类访问ACC_STATIC0x0008声明为staticACC_FINAL0x0010声明为final,不允许被子类继承ACC_SUPER0x0020当调用invokespecial指令时相应的去调用父类的构造方法ACC_INTERFACE0x0200声明为interface接口,不是类ACC_ABSTRACT0x0400声明为abstract,不能被实例化ACC_SYNTHETIC0x1000声明为synthetic,源代码中不存在ACC_ANNOTATION0x2000声明为annotation注解类型。ACC_ENUM0x4000声明为enum枚举

    5 字段表filed_info集合

    字段表用于描述类和接口中声明的变量 这里的字段包含了

    类级别变量实例变量

    但是不包括方法内部声明的局部变量 表4. 字段表filed_info结构

    类型名称数量u2access_flags—访问标志1u2name_index—字段名索引1u2descriptor_index—描述符索引1u2attribute_count—附加属性个数1attribute_infoattributesattribute_count

    完整的filed_info字节码结构

    field_info{ u2 access_flags; u2 name_index; u2 descriptor_index; u2 attribute_count; attributes[attribute_count]; }

    6 方法表method_info集合

    表5. 方法表method_info结构

    类型名称数量u2access_flags—访问表示1u2name_index—字段名索引1u2descriptor_index—描述符索引1u2attribute_count—附加属性个数1attribute_infoattributesattribute_count

    完整的method_info字节码结构

    method_info{ u2 access_flags; u2 name_index; u2 descriptor_index; u2 attribute_count; attributes[attribute_count]; }

    6.1 附加属性表attribute_info集合

    方法中的每个属性都是一个attribute_info结构JVM预定义了部分attribute,但是编译器自己也可以实现自己的attribute写入class文件里,共运行时使用不同的attribute通过attribute_name_index来区分

    表6. 附加属性表attribute_info结构

    类型名称数量u2attribute_name_index—属性名索引1u4attribute_length—属性名长度1u2info[attribute_length]—属性信息attribute_length

    完整的attribute_info字节码结构

    attribute_info{ u2 attribute_name_index; u4 attribute_length; u1 info[attribute_length]; }

    6.2 Code attribute结构

    Code attribute的作用是保存该方法的结构

    表7. 代码属性表Code_attribute结构

    类型名称数量u2attribute_name_index—属性名索引1u4attribute_length—attribute所包含的字节数.不包含attribute_name_index和attribute_length1u2max_stack—最大栈深度,表示这个方法运行时的任何时刻所能到达的操作数栈的最大深度1u2max_locals—最大局部变量数表示这个方法执行期间所创建的局部变量的数目,包含用来表示传入的参数的局部变量1u4code_length–该方法所包含的字节码的字节数以及具体的指令码11u1code[code_length]code_lengthu2exception_table_length—异常表长度1exception_tableexception_table[exception_table_length]异常表—存放的是处理异常的信息2exception_table_lengthu2attributes_count—异常表长度1attributesattributes[attributes_count]—异常表长度attributes_count

    如所对应的字节码

    Code_attribute{ u2 attribute_name_index; u4 attribute_length; u2 max_stack; u2 max_locals; u4 code_length; u1 code[code_length]; u2 exception_table_length; { u2 start_pc; u2 end_pc; u2 handler_pc; u2 catch_type; }exception_table[exception_table_length]; u2 attributes_count; attributes[attributes_count]; } start_pc和end_pc表示在code数组中从 start_pc到 end_pc(包含前者,不包含后者)的指令抛出的异常会由这个表项来处理handler_pc表示处理异常的代码的开始处.catch_type表示会被处理的异常类型,它指向常量池里的一个类,当catch_type为0时,表示处理所有异常

    Java字节码对异常的处理方式

    统一采用异常表的方式来对异常进行处理在JDK 1.4.2之前的版本中,并不是使用异常表的方式来对异常进行处理的,而是采用特定的指令方式当异常存在finally语句块时,现代化的JVM采取的处理方式是将finally语句块的字节码拼接到每一个catch块后面,换句话说,程序中至少存在多个catch块,就会在每一个catch语句块后面重复多少个finally语句块的字节码

    [1] 具体字节码即是该方法被调用时,虚拟机所执行的字节码 [2] 每个exception_table表项有start_pc, end_pc, handler_pc, catch_pc组成

    6.3 LineNumberTable的结构

    LineNumberTable{ u2 attribute_name_index; u4 attribute_length; u2 line_number_table_length; { u2 start_pc; u2 line_number; }line_number_table[line_number_table_length] }

    6.4 LocalVariableTable的结构

    对于Java类的每一个实例方法(非static方法),其在编译后所生成的字节码当中, 方法参数的数量总是会比源代码中方法参数的数量多一个(this).

    它位于方法的第一个参数位置处,这样我们就可以像Java的实例方法中使用this来访问当前对象的属性以及其他方法 这个操作是在编译期间完成的,即由javac编译器在编译时将对this的方法转化为对一个普通实例方法参数的访问

    接下来在运行期间,由JVM在调用实例方法时,自动向实例方法传入该this参数.

    所以.在实例方法的局部变量表中,至少会有一个指向当前对象的局部变量

    LocalVariableTable{ u2 attribute_name_index; u4 attribute_length; u2 local_variable_table_length; { u2 start_pc; u2 length; u2 name_index; u2 descripor_index; u2 index }local_variable_table[local_variable_table_length] }

    7 SourceFile的结构

    SourceFile{ u2 attribute_name_index; u4 attribute_length; u2 sourcefile_index; }
    最新回复(0)