• 类索引、父类索引与接口索引集合
  • 字段表集合
  • 方法表集合
  • 属性表集合

由于上一篇文章中实例代码没有声明字段,所以本篇内容变更示例代码

源码

1
2
3
4
5
6
7
8
9
package test;

public class Test {
private int m;

public int inc() {
return m + 1;
}
}

class文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
CA FE BA BE(Class魔数)
--------------版本号-------------------
00 00(次版本号)
00 35(主版本号)
--------------常量池-------------------
00 13(十进制19,18个常量)
--1--
0A(十进制10,CONSTANT_Methodref_info)
00 04(4,指向声明方法的类描述符CONSTANT_Class_info的索引项)
00 0F(15,指向名称及类型描述符CONSTANT_NameAndType_info的索引项)
--2--
09(十进制9,CONSTANT_Fieldref_info)
00 03(3,指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项)
00 10(16,指向名称及类型描述符CONSTANT_NameAndType_info的索引项)
--3--
07(CONSTANT_Class_info)
00 11(常量池中第17项)
--4--
07(CONSTANT_Class_info)
00 12(常量池中第18项)
--5--
01(CONSTANT_Utf8_info)
00 01(字符串长度是1字节)
6D(十进制109,字母m)
--6--
01(CONSTANT_Utf8_info)
00 01(字符串长度是1字节)
49(十进制73,字母I)
--7--
01(CONSTANT_Utf8_info)
00 06(字符串长度是6字节)
3C 69 6E 69 74 3E(<init>)
--8--
01(CONSTANT_Utf8_info)
00 03(字符串长度是3字节)
28 29 56(()v)
--9--
01(CONSTANT_Utf8_info)
0004(字符串长度是4字节)
43 6F 64 65(Code)
--10--
01(CONSTANT_Utf8_info)
00 0F(字符串长度是15字节)
4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65(LineNumberTable)
--11--
01(CONSTANT_Utf8_info)
00 03(字符串长度是3字节)
69 6E 63(inc)
--12--
01(CONSTANT_Utf8_info)
00 03(字符串长度是3字节)
28 29 49(()I)
--13--
01(CONSTANT_Utf8_info)
00 0A(字符串长度是10字节)
53 6F 75 72 63 65 46 69 6C 65(SourceFile)
--14--
01(CONSTANT_Utf8_info)
00 0E(字符串长度是14字节)
54 65 73 74 43 6C 61 73 73 2E 6A 61 76 61( TestClass.java)
--15--
0C(CONSTANT_NameAndType_info)
00 07(7,指向该字段或方法名称常量项的索引,<init>)
00 08(8,指向该字段或方法描述符常量项的索引,()v)
--16--
0C(CONSTANT_NameAndType_info)
00 05(5,指向该字段或方法名称常量项的索引,m)
00 06(6,指向该字段或方法描述符常量项的索引,I)
--17--
01(CONSTANT_Utf8_info)
00 0E(字符串长度是14字节)
74 65 73 74 2F 54 65 73 74 43 6C 61 73 73(test/TestClass)
--18--
01(CONSTANT_Utf8_info)
00 10(字符串长度是16字节)
6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74(java/lang/Object)
--------------访问标志-------------------
00 21(ACC_PUBLIC、ACC_SUPER,0x0001|0x0020=0x0021)
--------------类索引-------------------
00 03(3,this_class,类索引为3,常量池中第3项:test/TestClass)
--------------父类索引-------------------
00 04(4,super_class,父类索引为4,常量池中第4项:java/lang/Object)
(类索引、父类索引各自指向CONSTANT_Class_info的类描述符常量,通过CONSTANT_Class_info类型的常量中的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串)
--------------接口索引-------------------
00 00(0,interfaces_count,接口计数器,表示索引表的容量)
--------------字段表集合-------------------
00 01(计数器容量:定义了1个字段)
00 02 (访问标志:ACC_PRIVATE)
00 05(5,指向该字段或方法名称常量项的索引,m)
00 06(6,descriptor_index,I,int)
00 00(属性表计数器)
--------------方法表集合-------------------
00 02(计数器容量:2个方法,inc()和编译器增加的实例构造器<init>)
00 01(访问标志:ACC_PUBLIC)
00 07(名称索引:常量池第7项,<init>)
00 08(描述符索引:常量池第8项,()v)
00 01(属性表计数器:1项属性)
--------------属性表集合-------------------
---Code属性开始---
00 09(attribute_name_index:常量池第9项,Code)
00 00 00 1D(attribute_length:29)
00 01(max_stack:1)
00 01(max_locals:1)
00 00 00 05(code_length:5)
---
2A(对应指令为aload_0,将低0个Slot槽位为reference类型的本地变量推送到栈的顶端)
B7(对应指令为invokespecial,将栈顶的reference类型的数据所指向的对象作为方法的接收者,调用此对象的实例构造方法。这条指令带有一个u2类型的参数,为具体调用的哪一个方法,指向常量池中一个
CONSTANT_Methodref_info类型常量,即此方法的符号应用。 )
00 01(方法的符号引用,常量池中第1项,java/lang/Object <init>V)
B1(对应指令为return,含义为返回此方法,返回值为void。这条指令执行完后当前方法结束)
---
00 00(exception_table_length)
00 01(attributes_count:1)
---
00 0A(十进制:10,常量池中第10项,LineNumberTable)
00 00 00 06(属性长度:6)
00 01(line_number_table_length:1)
00 00(字节码编号:0)
00 03(Java源码行号:3)
---Code属性结束---
00 01(public)
00 0B(name_index:11,inc)
00 0C(description_index:12,()I)
00 01(属性计数器:1)
---Code属性开始---
00 09(属性名称:常量池第9项,Code)
00 00 00 1F(属性长度:31)
00 02(max_stack:2)
00 01(max_locals:1)
00 00 00 07(code_length:7)
2A(对应指令为aload_0,将低0个Slot槽位为reference类型的本地变量推送到栈的顶端)
B4(getfield,获取对象字段的值)
00 02(方法的符号引用,常量池中第2项,m和i)
04(iconst_1,1(int)值入栈)
60(iadd,将栈顶两int类型数相加,结果入栈)
AC(ireturn,返回int类型值)
00 00(exception_table_length)
00 01(attribute_count)
00 0A(十进制:10,常量池中第10项,LineNumberTable)
00 00 00 06(属性长度:6)
00 01(line_number_table_length:1)
00 00(字节码编号:0)
00 07(Java源码行号:7)
---Code属性结束---
--sourceFile--
00 01(public)
00 0D(name_index:13,SourceFile)
00 00 00 02(attribute_length:2)
00 0E (soucefile_index:14,TestClass.java)

类索引(this_class)

作用

  • 确定这个类的全限定名
  • 指向CONSTANT_Class_info的类描述符常量,通过CONSTANT_Class_info类型的常量中的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串

位置

访问标志之后

内容

  • 一个u2类型的数据

实例

1
00 01(1,this_class,类索引为1,常量池中第一项:test/Test)

父类索引(super_class)

作用

  • 确定这个类的父类全限定名
  • 父类索引各自指向CONSTANT_Class_info的类描述符常量,通过CONSTANT_Class_info类型的常量中的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串

位置

  • 类索引之后

内容

  • 一个u2类型的数据

特点

  • Java不允许多重继承,父类索引只有一个
  • 除了java.lang.Object之外,所有类都有父类,父类都不为0

实例

1
00 03(3,super_class,父类索引为3,常量池中第三项:java/lang/Object)

接口索引集合(interfaces)

作用

  • 描述这个类实现了哪些接口
    • 这些被实现的接口将按implements语句(如果类本身是一个接口,则应当是extends语句)后的接口顺序从左到右排列在接口索引集合中

位置

父类索引之后

内容

  • u2类型的数据:接口计数器interfaces_count
    • 表示索引表的容量
    • 如果没有实现任何接口,则为0

实例

1
00 00(0,interfaces_count,接口计数器,表示索引表的容量,0为没有实现任何接口)

Class文件中由这三项数据来确定这个类的继承关系

字段表集合

作用

字段表field_info,用来描述接口或者类中声明的变量

字段field,包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量

位置

接口索引后

内容

  • 字段的作用域(public、private、protected)
  • 是实例变量还是类变量(static修饰符)
  • 可变性(final)
  • 并发可见性(volatile,是否强制从主内存读写)
  • 是否被序列化(transient修饰符)

上述各个修饰符都是布尔值,用标志位来表示

  • 字段名字
  • 字段的数据类型

上述内容不固定,只能引用常量池中的常量来描述。

字段表结构见下表

类型 名称 数量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attributes_info attributes attributes_count

access_flags字段访问标志见下表

标志名称 标志值 含义
ACC_PUBLIC 0x0001 字段是否public
ACC_PRIVATE 0x0002 字段是否private
ACC_PROTECTED 0x0004 字段是否protected
ACC_STATIC 0x0008 字段是否static
ACC_FINAL 0x0010 字段是否final
ACC_VOLATILE 0x0040 字段是否volatile
ACC_TRANSIENT 0x0080 字段是否transient
ACC_SYNTHETIC 0x1000 字段是否由编译器自动产生的
ACC_ENUM 0x4000 字段是否enum

特点

  • ACC_PUBLICACC_PRIVATEACC_PROTECTED三者只能选其一
  • ACC_FINALACC_VOLATILE二者只能选其一
  • 接口中字段必须有ACC_PUBLICACC_STATICACC_FINAL标志,Java语言规则决定。

说明

  • name_index
    • 对常量池的引用
    • 字段的简单名称
  • descriptor_index
    • 对常量池的引用
    • 字段和方法的描述符
  • 全限定名
    • test/TestClass
    • ;分隔
  • 简单名称
    • 没有类型和参数修饰的方法或者字段名称
    • minc
  • 描述符
    • 用来描述字段的数据类型、方法的参数列表(数量、类型、顺序)、返回值
  • 描述符标识字符含义表
标识字符 含义
B byte
C char
D double
F float
I int
J long
S short
Z boolean
V void
L 对象类型,如Ljava/lang/Object
  • 基本数据类型及无返回值:大写字母
  • 对象类型:L+对象的全限定名
  • 数组:每一维度用一个前置的[
    • int[] > [I
    • java.lang.String[][] > [[Ljava/lang/String;
  • 方法:

    • 顺序:先参数列表,后返回值
      • void inc() > ()V
      • java.lang.String toString() > ()Ljava/lang/String;
      • int testF(char[] args1, int arg2 , char[] arg3) > ([CI[C)I
  • 字段表中不会列出超类或者父接口中继承而来的字段

  • 但有可能列出原本Java代码中不存在的字段

    • 内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段
  • Java语言中字段是无法重载的,两个字段的数据类型、修饰符不管是否相同,都必须使用不一样的名称,但是对于字节码来讲,如果两个字段的描述符不一致,那字段重名就是合法的

实例

1
2
3
4
5
6
--------------字段表集合-------------------
00 01(计数器容量:定义了1个字段)
00 02 (访问标志:ACC_PRIVATE)
00 05(5,指向该字段或方法名称常量项的索引,m)
00 06(6,descriptor_index,I,int)
00 00(属性表计数器)

方法表集合

作用

method_info,对方法的描述

位置

字段表之后

内容

方法表结构见下表

类型 名称 数量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attributes_info attributes attributes_count

方法访问标志见下表

标志名称 标志值 含义
ACC_PUBLIC 0x0001 方法是否public
ACC_PRIVATE 0x0002 方法是否private
ACC_PROTECTED 0x0004 方法是否protected
ACC_STATIC 0x0008 方法是否static
ACC_FINAL 0x0010 方法是否final
ACC_SYNCHRONIZED 0x0020 方法是否synchronized
ACC_BRIDGE 0x0040 方法是否由编译器产生的桥接方法
ACC_VARARGS 0x0080 方法是否接受不定参数
ACC_NATIVE 0x0100 方法是否native
ACC_ABSTRACT 0x0400 方法是否abstract
ACC_STRICTFP 0x0800 方法是否strictfp
ACC_SYNTHETIC 0x1000 方法是否由编译器自动产生

说明

  • 方法里的代码,经过编译器编译成字节码指令后,存在方法属性表集合中一个名为Code的属性里面,之后有详解

  • 如果父类方法在子类中没有被重写(Override),方法表集合中就不会出现来自父类的方法信息

  • 可能会出现由编译器自动添加的方法,之后有详解

    • 最典型:类构造器<clinit>
    • 实例构造器<init>
  • 根据虚拟机规范中对重载的定义(4.7.9),特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合(不包含返回值),也就是说无法只依靠返回值的不同来对一个已有方法进行重载。但是在Class文件格式中,特征签名只要描述符不是完全一致的两个方法也可以共存,也就是说,如果两个方法有相同的名称和特征签名,但返回值不同,那么也是可以合法共存于同意Class文件中。

实例

1
2
3
4
5
6
--------------方法表集合-------------------       
00 02(计数器容量:2个方法,inc()和编译器增加的实例构造器<init>)
00 01(访问标志:ACC_PUBLIC)
00 07(名称索引:常量池第7项,<init>)
00 08(描述符索引:常量池第8项,()v)
00 01(属性表计数器:1项属性)

属性表集合

作用

attribute_info,用于描述某些场景专有的信息

位置

方法表之后

内容

为了能正确解析Class文件,在最新的JVM规范中,预定义了一些属性4.7

其中,4个属性对于Java正确解释类文件至关重要

属性名称 使用位置 含义
Code 方法表 Java代码编译成的字节码指令
ConstantValue 字段表 final关键字定义的常量值
StackMapTable Code属性 方法的局部变量描述
BootstrapMethods 类文件 JDK1.7新增,用于保存invokedynamic指令引用的引导方法限定符

其中,9个属性对JVM解释Class文件不重要,但对Java SE的类库或者工具解释Class文件有用,可选

属性名称 使用位置 含义
Exceptions 方法表 方法抛出的异常
InnerClasses 类文件 内部类列表
EnclosingMethod 类文件 仅当一个类为局部类或者匿名类时才能拥有这个属性,标识着歌类所在的外围方法
Synthetic 类、方法表、字段表 标识方法或字段为编译器自动生成的
Signature 类、方法表、字段表 JDK1.5新增,用于支持泛型情况下的方法签名。在Java语言中,任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量或参数化类型,由于Java的泛型采用擦除法实现,在为了避免类型信息被擦除后导致签名混乱,这个属性会记录泛型签名信息。
SourceFile 类文件 记录源文件名称
LineNumberTable Code属性 Java源码的行号与字节码指令的对应关系
LocalVariableTable 方法的局部变量描述
LocalVariableTypeTable JDK1.5新增,它使用特征签名代替描述符,是为了引入泛型语法后能描述泛型参数化类型而添加

其中,13个属性对JVM解释Class文件不重要,但包含有关类文件的元数据,这些元数据要么由Java SE平台的类库公开,要么由工具提供,可选

属性名称 使用位置 含义
SourceDebugExtension JDK1.6新增,用于存储额外的调试信息。譬如在进行JSP文件调试时,无法通过Java堆栈定位到JSP文件的行号,JSR-45规范为这些非Java语言编写,却需要编译成字节码并运行在JVM中的程序提供了一个进行调试的标准机制。
Deprecated 类、方法表、字段表 被声明为deprecated的方法和字段
RuntimeVisibleAnnotations 类、方法表、字段表 JDK1.5新增,为动态注解提供支持,用于指明哪些注解是运行时(反射调用)可见的
RuntimeInvisibleAnnotations 类、方法表、字段表 JDK1.5新增,为动态注解提供支持,用于指明哪些注解是运行时(反射调用)不可见的
RuntimeVisibleParameterAnnotations 方法表 JDK1.5新增,用于指明哪些方法参数是运行时(反射调用)可见的
RuntimeInvisibleParameterAnnotations 方法表 JDK1.5新增,用于指明哪些方法参数是运行时(反射调用)不可见的
RuntimeVisibleTypeAnnotations 类、方法表、字段表、Code属性 可变长度属性,记录使用的类型运行时可见注释。还记录泛型类、接口、方法和构造函数的类型参数声明的注释。最多有一个。
RuntimeInvisibleTypeAnnotations 类、方法表、字段表、Code属性 可变长度属性,记录使用的类型运行时不可见注释。还记录泛型类、接口、方法和构造函数的类型参数声明的注释。最多有一个。
AnnotationDefault 方法表 可变长度属性,记录使用的注释类型元素的默认值,最多只有一个
MethodParameters 方法表 可变长度属性,记录有关方法的形式参数的信息,例如它们的名称,最多只有一个
Module 类的属性表 可变长度属性,记录模块所需的模块;模块导出和打开的包;以及模块使用和提供的服务,最多只有一个
ModulePackages 类的属性表 可变长度属性,记录模块属性导出或打开的模块的所有包,以及记录在Module属性中的服务实现的所有包,还可以记录模块中既不导出也不打开或包含服务实现的包
ModuleMainClass 类的属性表 固定长度属性,记录模块的主类

属性表结构

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u1 info attribute_length

说明

  • 不要求各个属性表具有严格顺序,并且只要不与已有属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,JVM运行时会忽略掉不认识的属性

  • 每个属性的名称需要从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示,属性之的结构可以完全自定义,只需要通过一个u4长度的属性说明占用的位数即可。

  • 具体属性格式可查询JVM规范(4.7.2-4.7.27)

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
---Code属性开始---       
00 09(attribute_name_index:常量池第9项,Code)
00 00 00 1D(attribute_length:29)
00 01(max_stack:1)
00 01(max_locals:1)
00 00 00 05(code_length:5)
---
2A(对应指令为aload_0,将低0个Slot槽位为reference类型的本地变量推送到栈的顶端)
B7(对应指令为invokespecial,将栈顶的reference类型的数据所指向的对象作为方法的接收者,调用此对象的实例构造方法。这条指令带有一个u2类型的参数,为具体调用的哪一个方法,指向常量池中一个
CONSTANT_Methodref_info类型常量,即此方法的符号应用。 )
00 01(方法的符号引用,常量池中第1项,java/lang/Object <init>V)
B1(对应指令为return,含义为返回此方法,返回值为void。这条指令执行完后当前方法结束)
---
00 00(exception_table_length)
00 01(attributes_count:1)
---
00 0A(十进制:10,常量池中第10项,LineNumberTable)
00 00 00 06(属性长度:6)
00 01(line_number_table_length:1)
00 00(字节码编号:0)
00 03(Java源码行号:3)
---Code属性结束---
00 01(public)
00 0B(name_index:11,inc)
00 0C(description_index:12,()I)
00 01(属性计数器:1)
---Code属性开始---
00 09(属性名称:常量池第9项,Code)
00 00 00 1F(属性长度:31)
00 02(max_stack,操作数栈深度的最大值:2)
00 01(max_locals,局部变量表所需的存储空间:1(slot))
00 00 00 07(code_length:7)
2A(对应指令为aload_0,将低0个Slot槽位为reference类型的本地变量推送到栈的顶端)
B4(getfield,获取对象字段的值)
00 02(方法的符号引用,常量池中第2项,m和i)
04(iconst_1,1(int)值入栈)
60(iadd,将栈顶两int类型数相加,结果入栈)
AC(ireturn,返回int类型值)
00 00(exception_table_length)
00 01(attribute_count)
00 0A(十进制:10,常量池中第10项,LineNumberTable)
00 00 00 06(属性长度:6)
00 01(line_number_table_length:1)
00 00(字节码编号:0)
00 07(Java源码行号:7)
---Code属性结束---
--sourceFile--
00 01(public)
00 0D(name_index:13,SourceFile)
00 00 00 02(attribute_index:2)
00 0E (soucefile_index:14,TestClass.java)

Slot:JVM为局部变量分配内存所使用的最小单位,长度不超过32位的数据类型(byte、char、float、int、short、boolean和returnAddress)占用1Slot,64位的数据类型(double、long)需要2Slot来存放。当代码执行超过一个局部变量的作用域时,这个局部变量所占的Slot可以被其他局部变量所使用,Javac编译器会根据变量的作用与来分配Slot给各个变量使用,然后计算max_locals的大小.

工具

Chrome Plugin-Binary Converter

参考资料

JVM规范-JDK1.10

Java bytecode instruction listings