1 类文件数据结构类型
Class文件结构主要有两种数据结构:无符号数和表
•无符号数:用来表述数字,索引引用、数量值以及字符串等,比如 图1中类型为u1,u2,u4,u8分别代表1个字节,2个字节,4个字节,8个字节的无符号数
•表:表是有由多个无符号数以及其它的表组成的复合结构,比如图1中类型以_info结尾的项为表类型。
2 类结构定义
Class类文件是紧凑、顺序、无空隙的,魔数(MagicNumber)、Class文件版本(Version)、常量池(Constant_Pool)、访问标记(Access_flag)、本类(This_class)、父类(Super_class)、接口(Interfaces)、字段集合(Fields)、方法集合(Methods )、属性集合(Attributes)。其中因为java多继承所以interfaces接口类型为数组;attribute_info则是方法表中定义的code索引,指向具体的方法体字节码。如图1所示。

下面用一段程序做说明,此类有接口,有方法、类变量和实例变量,机器是如何识别字节码然后按照上面的规则来定义此class类呢?- package com.jd.crm.Logback;
- public class TestClass implements Super{
- private static final int staticVar = 0;
- private int instanceVar=0;
- public int instanceMethod(int param) throws Exception{
- return param ++;
- }
- }
- interface Super{ }
复制代码 通过javap帮助解析class文件格式如下:- Classfile /D:/spm-workspace/test/target/classes/com/jd/crm/Logback/TestClass.class
- Last modified 2023-4-14; size 597 bytes
- MD5 checksum 9d5dd9fc2145ac17393fee7a707d3b9c
- Compiled from "TestClass.java"
- public class com.jd.crm.Logback.TestClass implements com.jd.crm.Logback.Super
- minor version: 0
- major version: 52
- flags: ACC_PUBLIC, ACC_SUPER
- Constant pool:
- #1 = Methodref #4.#26 // java/lang/Object."<init>":()V
- #2 = Fieldref #3.#27 // com/jd/crm/Logback/TestClass.instanceVar:I
- #3 = Class #28 // com/jd/crm/Logback/TestClass
- #4 = Class #29 // java/lang/Object
- #5 = Class #30 // com/jd/crm/Logback/Super
- #6 = Utf8 staticVar
- #7 = Utf8 I
- #8 = Utf8 ConstantValue
- #9 = Integer 0
- #10 = Utf8 instanceVar
- #11 = Utf8 <init>
- #12 = Utf8 ()V
- #13 = Utf8 Code
- #14 = Utf8 LineNumberTable
- #15 = Utf8 LocalVariableTable
- #16 = Utf8 this
- #17 = Utf8 Lcom/jd/crm/Logback/TestClass;
- #18 = Utf8 instanceMethod
- #19 = Utf8 (I)I
- #20 = Utf8 param
- #21 = Utf8 Exceptions
- #22 = Class #31 // java/lang/Exception
- #23 = Utf8 MethodParameters
- #24 = Utf8 SourceFile
- #25 = Utf8 TestClass.java
- #26 = NameAndType #11:#12 // "<init>":()V
- #27 = NameAndType #10:#7 // instanceVar:I
- #28 = Utf8 com/jd/crm/Logback/TestClass
- #29 = Utf8 java/lang/Object
- #30 = Utf8 com/jd/crm/Logback/Super
- #31 = Utf8 java/lang/Exception
- {
- public com.jd.crm.Logback.TestClass();
- descriptor: ()V
- flags: ACC_PUBLIC
- Code:
- stack=2, locals=1, args_size=1
- 0: aload_0
- 1: invokespecial #1 // Method java/lang/Object."<init>":()V
- 4: aload_0
- 5: iconst_0
- 6: putfield #2 // Field instanceVar:I
- 9: return
- LineNumberTable:
- line 3: 0
- line 7: 4
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 10 0 this Lcom/jd/crm/Logback/TestClass;
- public int instanceMethod(int) throws java.lang.Exception;
- descriptor: (I)I
- flags: ACC_PUBLIC
- Code:
- stack=1, locals=2, args_size=2
- 0: iload_1
- 1: iinc 1, 1
- 4: ireturn
- LineNumberTable:
- line 10: 0
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 5 0 this Lcom/jd/crm/Logback/TestClass;
- 0 5 1 param I
- Exceptions:
- throws java.lang.Exception
- MethodParameters:
- Name Flags
- param
- }
- SourceFile: "TestClass.java"
复制代码 以上是javap帮助我们生成的class文件解析结果,只是给人看,而非机器。
通过编译后生成class文件格式如下,因为class文件是以8位作为一个字节的二进制流。为了方便计算,用16进制表示二进制(1个字节=2个十六进制的数,故下面每2个数就代表1个字节)

2.1 魔法数
前四个字节cafebabe是固定值,任何语言编译成jvm认识的二进制流,前四位必须是固定的cafebabe字节。
2.2 版本号
紧接着2个字节00表示次版本号为0 ;0034代表主版本为52(jdk版本号对应的jdk版本为1.8)参考jdk版本和class字节版本的对应关系
2.3 常量个数
常量个数const_pool_count字节码为00 20对应的说明常量个数为32,实际为31个,因为首位jvm作为保留位使用。
2.4 常量池
常量池存放两大常量:字面量和符号引,字面量如文本字符串,被生命的final常量值等,而符号引用则包含类、接口的全限名称、字段、方法名称和描述符号等等。参考javap生成的类文件信息。
这里只分析下其中一个常量,在上面常量个数2个字节后面紧接着一个字节0a十进制为10,参考常量池类型10代表类中方法的符号引用。继续参考方法类型MethodRef_info个格式定义:前两个字节0004代表方法所在类名称的索引,后两个字节0001a代表一个NameAndType类型的索引。

2.5 类访问标志
紧接常量池定义完后的u2标识访问标志,本例标识为0x0021和下图标志位按位或计算,如0x0001为真,0x0020也为真,其他为否 最终确认访问标志位ACC_PUBLIC、ACC_SUPER

2.6 本类、父类、接口索引集合
根据图1的规则,u2两个字节0003标识当前类名的引用到,引用常量池数组下标为#3,根据图3所示子项的类名为com/jd/crm/Logback/TestClass;0004代表父类类名的引用常量池数组下标为#4,根据图4所示引用的父类类名为java/lang/Object;紧接着0001标识接口个数,指明数量为1,0005标识第一个接口数组中接口的名称,指向常量池中下标为5的名称为com/jd/crm/Logback/Super;
比如查找当前类索引如下图

2.7 字段表集合
字段表以数组的形式定义存储在常量表中

以上图说明,0002标识域个数为2个域标识,在本类中有两个,一个类的域字段staticVar 一个是实例对象的域字段instanceVar,如字段结构定义(下图)定义,前2个字节001a为访问标识,和类访问标识一样,分别用001a的二进制和下图字段域访问标识类型做位或运算,得出访问类型为ACC_PRIVATE类型。name_index的占用两个字节0006,指向常量表下标为6的引用,descriptor_index=0007指向常量表下标为7的引用,此处为I标识为数据类型为int,attributes_count=0001为1个,值为0008指向常量表下标为#8的引用常量ConstantValue,标识为静态变量,最终依次类推第二个域标识引用

字段结构定义
字段域的访问标志请参考类访问标志,逻辑计算一致,只是规则不一样而已 如下图

2.8 方法表集合
和域字段集合表定义类似 也是数组方式定义在常量池中 ,其中方法的结构体第四个字段attributes_count代表方法的属性数量,attribute_info就是属性的集合参考属性表集合


方法表访问标识类型
通过上面方法的访问标志、名称索引和描述索引定义方法的基本信息,方法的代码块则存放于类型为Code的属性表中。
2.9 属性表集合
类、字段表、方法表本身可包含属性表,属性表格结构体如下,属性表结构类型较多,比如有Code类型、Exception类型、MethodParameters类型等等,具体参考属性表类型。所有的属性都是引用常量池中的属性类型名称。然后根据属性的长度指定该属性的内容,根据属性的不同类型解析不同的属性值。格式定义如下

以Code属性举例,Code属性结构如下所示

jvm按属性获取attribute_name_index指向常量池一个字符串常量Code,紧接着attribute_length标识Code类型Info信息长度,这个info内容包括:max_stack 最大栈深,max_locals局部变量槽数量,code_length标识机器字节码长度,往后查询字节码如下图所示,其实就是0/1/4/5/6/9的指令集。Code类型又嵌套异常属性表、行号表LineNumberTable、LocaVariableTable 局部变量表等等信息。如下图javap生成的类定义信息

1.Code1方法执行过程:
构造方法:descriptor ()V标识无参无返回值为Void的方法索引,flags可见性修饰符;
程序运行时,先将常量池、方法字节码、字符串常量池,静态变量加载到元数据区(1.8后字符串常量池,静态变量放入了堆);main线程开始运行,分配栈帧内存,其中操作数栈stack=2表示运行该方法所需要的最大操作数栈的深度是2;locals=1表示该运行方法所需要的最大局部方法表的最大slot数据是1;args_size是该方法的形参个数,如果是实例方法 第一个形参是this引用。此例正是this引用。所以args_size=1+实际的参数
aload_0: 加载 slot0的局部变量,即this,作为下面的invokespecial 构造方法调用的参数
invokespecial: 调用构造方法,常量池第#1项,即【Method java/lang/Object.""
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |