1.类文件结构
一个简单的HelloWorld.java:
1 | public class HelloWorld { |
执行 javac 命令:
1 | javac -parameters -d . HelloWorld.java |
得到HelloWorld.class文件
这里使用的是Windows环境,使用UltraEdit或者WinHex工具打开class文件。如果是Linux环境,可以使用如下命令打开class文件:
1 | od -t xC HelloWorld.class |
打开class文件结果如下:
根据JVM规范,类文件结果如下:
1 | ClassFile { |
参考资料:Oracle官方文档
1.1 魔数
1 | u4 magic; |
The magic item supplies the magic number identifying the class file format; it has the value 0xCAFEBABE.
1 | 00000000h: CA FE BA BE 00 00 00 34 00 23 0A 00 06 00 15 09 |
0-3字节,CA FE BA BE 表示是否是class类型的文件
1.2 版本
1 | u2 minor_version; |
1 | 00000000h: CA FE BA BE 00 00 00 34 00 23 0A 00 06 00 15 09 |
4-7字节,00 00 00 34 表示类的版本
0x34(十进制52)表明是java8
1.3 常量池
1 | u2 constant_pool_count; |
Constant Type | Value |
---|---|
CONSTANT_Class | 7 |
CONSTANT_Fieldref | 9 |
CONSTANT_Methodref | 10 |
CONSTANT_InterfaceMethodref | 11 |
CONSTANT_String | 8 |
CONSTANT_Integer | 3 |
CONSTANT_Float | 4 |
CONSTANT_Long | 5 |
CONSTANT_Double | 6 |
CONSTANT_NameAndType | 12 |
CONSTANT_Utf8 | 1 |
CONSTANT_MethodHandle | 15 |
CONSTANT_MethodType | 16 |
CONSTANT_InvokeDynamic | 18 |
1 | 00000000h: CA FE BA BE 00 00 00 34 00 23 0A 00 06 00 15 09 |
8-9字节,表示常量池长度,00 23(十进制35)表示常量池有#1-#34项,注意#0项不计入,也没有值
cp_info: #1-#34
第#1项 0A 00 06 00 15
0A表示一个Method信息CONSTANT_Methodref,00 06(十进制6) 和 00 15(十进制21)表示它引用了常量池中#6和#21项来获得这个方法的[所属类]和[方法名]
第#2项 09 00 16 00 17
09表示一个Field信息CONSTANT_Fieldref,00 16(十进制22) 和 00 17(十进制23)表示它引用了常量池中#22和#23项来获得这个方法的[所属类]和[成员变量名]
第#3项 08 00 18
08表示一个字符串常量名称CONSTANT_String,00 18(十进制24)表示它引用了常量池中#24项
第#4项 0A 00 19 00 1A
0A表示一个Method信息CONSTANT_Methodref,00 19(十进制25) 和 00 1A(十进制26)表示它引用了常量池中#25和#26项来获得这个方法的[所属类]和[方法名]
第#5项 07 00 1B
07表示一个Class信息CONSTANT_Class,00 1B(十进制27)表示它引用了常量池中#27项
第#6项 07 00 1C
07表示一个Class信息CONSTANT_Class,00 1C(十进制28)表示它引用了常量池中#28项
第#7项 01 00 06 3C 69 6E 69 74 3E
01表示一个Utf8串CONSTANT_Utf8,00 06(十进制6) 表示长度,3C 69 6E 69 74 3E 是【
第#8项 01 00 03 28 29 56
01表示一个Utf8串CONSTANT_Utf8,00 03(十进制3)表示长度,28 29 56是【()V】其实就是表示无参,无返回值
第#9项 01 00 04 43 6F 64 65
01表示一个Utf8串CONSTANT_Utf8,00 04(十进制4)表示长度, 43 6F 64 65是【Code】
第#10项 01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65
01表示一个Utf8串CONSTANT_Utf8,00 0F(十进制15)表示长度, 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 是【LineNumberTable】
第#11项 01 00 12 4C 6F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65
01表示一个Utf8串CONSTANT_Utf8,00 12(十进制18) 表示长度, 4C 6F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65是【LocalVariableTable】
第#12项 01 00 04 74 68 69 73
01表示一个Utf8串CONSTANT_Utf8,00 04(十进制4)表示长度,74 68 69 73 是【this】
第#13项 01 00 24 4C 70 75 62 2F 69 79 75 2F 73 65 2F 6A 76 6D 64 61 79 2F 64 61 79 30 33 2F 48 65 6C 6C 6F 57 6F 72 6C 64 3B
01表示一个Utf8串CONSTANT_Utf8,00 24(十进制36)表示长度,4C 70 75 62 2F 69 79 75 2F 73 65 2F 6A 76 6D 64 61 79 2F 64 61 79 30 33 2F 48 65 6C 6C 6F 57 6F 72 6C 64 3B是【Lpub/iyu/se/jvmday/day03/HelloWorld;】
第#14项 01 00 04 6D 61 69 6E
01表示一个Utf8串CONSTANT_Utf8,00 04(十进制4)表示长度,6D 61 69 6E是【main】
第#15项 01 00 16 28 5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56
01表示一个Utf8串CONSTANT_Utf8,00 16(十进制22)表示长度,5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56 是【([Ljava/lang/String;)V】其实就是参数为字符串数组,无返回值
第#16项 01 00 04 61 72 67 73
01表示一个Utf8串CONSTANT_Utf8,00 04(十进制4)表示长度,61 72 67 73是【args】
第#17项 01 00 13 5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B
01表示一个Utf8串CONSTANT_Utf8,00 13(十进制19)表示长度,5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B是【Ljava/lang/String;】
第#18项 01 00 10 4D 65 74 68 6F 64 50 61 72 61 6D 65 74 65 72 73
01表示一个Utf8串CONSTANT_Utf8,00 10(十进制16)表示长度,4D 65 74 68 6F 64 50 61 72 61 6D 65 74 65 72 73是【MethodParameters】
第#19项 01 00 0A 53 6F 75 72 63 65 46 69 6C 65
01表示一个Utf8串CONSTANT_Utf8,00 0A(十进制10)表示长度,53 6F 75 72 63 65 46 69 6C 65是【SourceFile】
第#20项 01 00 0F 48 65 6C 6C 6F 57 6F 72 6C 64 2E 6A 61 76 61
01表示一个Utf8串CONSTANT_Utf8,00 0F(十进制15)表示长度,48 65 6C 6C 6F 57 6F 72 6C 64 2E 6A 61 76 61是【HelloWorld.java】
第#21项 0C 00 07 00 08
0C表示一个【名+类型】CONSTANT_NameAndType,00 07 00 08 引用了常量池中#7和#8两项
第#22项 07 00 1D
07表示一个Class信息CONSTANT_Class,00 1D(十进制29)表示它引用了常量池中#29项
第#23项 0C 00 1E 00 1F
0C表示一个【名+类型】CONSTANT_NameAndType,00 1E (十进制30)和 00 1F(十进制31)表示它引用了常量池中#30和#31两项
第#24项 01 00 0B 48 65 6C 6C 6F 20 77 6F 72 6C 64
01表示一个Utf8串CONSTANT_Utf8,00 0B(十进制11)表示长度,48 65 6C 6C 6F 20 77 6F 72 6C 64是【Hello world】
第#25项 07 00 20
07表示一个Class信息CONSTANT_Class,00 20(十进制32) 表示它引用了常量池中#32项
第#26项 0C 00 21 00 22
0C表示一个【名+类型】CONSTANT_NameAndType,00 21(十进制33) 和 00 22(十进制34)表示它引用了常量池中#33和#34两项
第#27项 01 00 22 70 75 62 2F 69 79 75 2F 73 65 2F 6A 76 6D 64 61 79 2F 64 61 79 30 33 2F 48 65 6C 6C 6F 57 6F 72 6C 64
01表示一个Utf8串CONSTANT_Utf8,00 22(十进制34)表示长度,70 75 62 2F 69 79 75 2F 73 65 2F 6A 76 6D 64 61 79 2F 64 61 79 30 33 2F 48 65 6C 6C 6F 57 6F 72 6C 64是【pub/iyu/se/jvmday/day03/HelloWorld】
第#28项 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74
01表示一个Utf8串CONSTANT_Utf8,00 10(十进制16)表示长度,6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74是【java/lang/Object】
第#29项 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 53 79 73 74 65 6D
01表示一个Utf8串CONSTANT_Utf8,00 10(十进制16)表示长度,6A 61 76 61 2F 6C 61 6E 67 2F 53 79 73 74 65 6D是【java/lang/System】
第#30项 01 00 03 6F 75 74
01表示一个Utf8串CONSTANT_Utf8,00 03(十进制3)表示长度,6F 75 74是【out】
第#31项 01 00 15 4C 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D 3B
01表示一个Utf8串CONSTANT_Utf8,00 15(十进制21)表示长度,4C 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D 3B是【Ljava/io/PrintStream;】
第#32项 01 00 13 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D
01表示一个Utf8串CONSTANT_Utf8,00 13(十进制19)表示长度,6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D是【java/io/PrintStream】
第#33项 01 00 07 70 72 69 6E 74 6C 6E
01表示一个Utf8串CONSTANT_Utf8,00 07(十进制7)表示长度,70 72 69 6E 74 6C 6E是【println】
第#34项 01 00 15 28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56
01表示一个Utf8串CONSTANT_Utf8,00 15(十进制21)表示长度,28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56 是【(Ljava/lang/String;)V】
1.4 访问标识与继承信息
1 | u2 access_flags; |
Field access and property flags:
Flag Name | Value | Interpretation |
---|---|---|
ACC_PUBLIC | 0x0001 | Declared public; may be accessed from outside its package. |
ACC_PRIVATE | 0x0002 | Declared private; usable only within the defining class. |
ACC_PROTECTED | 0x0004 | Declared protected; may be accessed within subclasses. |
ACC_STATIC | 0x0008 | Declared static. |
ACC_FINAL | 0x0010 | Declared final; never directly assigned to after object construction (JLS §17.5). |
ACC_VOLATILE | 0x0040 | Declared volatile; cannot be cached. |
ACC_TRANSIENT | 0x0080 | Declared transient; not written or read by a persistent object manager. |
ACC_SYNTHETIC | 0x1000 | Declared synthetic; not present in the source code. |
ACC_ENUM | 0x4000 | Declared as an element of an enum. |
1 | 000001c0h: 00 21 00 05 00 06 00 00 00 00 00 02 00 01 00 07 |
00 21 表示该class是一个类,公共的
00 05 表示根据常量池中#5找到本类全限定名
00 06 表示根据常量池中#6找到父类全限定名
00 00 表示接口的数量,本类为0
1.5 Field信息
1 | u2 fields_count; |
1 | 000001c0h: 00 21 00 05 00 06 00 00 00 00 00 02 00 01 00 07 |
00 00 表示成员变量数量,本类为0
FieldType term | Type | Interpretation |
---|---|---|
B | byte | signed byte |
C | char | Unicode character code point in the Basic Multilingual Plane, encoded with UTF-16 |
D | double | double-precision floating-point value |
F | float | single-precision floating-point value |
I | int | integer |
J | long | long integer |
L ClassName ; | reference | an instance of class ClassName |
S | short | signed short |
Z | boolean | true or false |
[ | reference | one array dimension |
1.6 Method信息
1 | u2 methods_count; |
Method access and property flags:
Flag Name | Value | Interpretation |
---|---|---|
ACC_PUBLIC | 0x0001 | Declared public; may be accessed from outside its package. |
ACC_PRIVATE | 0x0002 | Declared private; accessible only within the defining class. |
ACC_PROTECTED | 0x0004 | Declared protected; may be accessed within subclasses. |
ACC_STATIC | 0x0008 | Declared static. |
ACC_FINAL | 0x0010 | Declared final; must not be overridden (§5.4.5). |
ACC_SYNCHRONIZED | 0x0020 | Declared synchronized; invocation is wrapped by a monitor use. |
ACC_BRIDGE | 0x0040 | A bridge method, generated by the compiler. |
ACC_VARARGS | 0x0080 | Declared with variable number of arguments. |
ACC_NATIVE | 0x0100 | Declared native; implemented in a language other than Java. |
ACC_ABSTRACT | 0x0400 | Declared abstract; no implementation is provided. |
ACC_STRICT | 0x0800 | Declared strictfp; floating-point mode is FP-strict. |
ACC_SYNTHETIC | 0x1000 | Declared synthetic; not present in the source code. |
1 | 000001c0h: 00 21 00 05 00 06 00 00 00 00 00 02 00 01 00 07 |
00 02 表示方法数量,本类为2
一个方法由 访问修饰符 + 名称 + 参数描述 + 方法属性数量 + 方法属性 组成
- 00 01 代表访问修饰符(本类中是public)
- 00 07 代表引用了常量池#07项作为方法名称
- 00 08 代表引用了常量池#08项作为方法参数描述
- 00 01 代表方法属性数量,本方法是1
- 方法属性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
201. 00 09 表示引用了常量池#09项,是【Code】属性
2. 00 00 00 2F 表示此属性的长度是47
3. 00 01 表示【操作数栈】最大深度
4. 00 01 表示【局部变量表】最大槽数(slot)
5. 00 00 00 05 表示字节码长度,本例是5
6. 2A B7 00 01 B1 是字节码指令
7. 00 00 00 02 表示方法细节属性数量,本例是2
8. 00 0A 表示引用了常量池#10项,是【LineNumberTable】属性
00 00 00 06 表示此属性的总长度,本例是6
00 01 表示【LineNumberTable】长度
00 00 表示【字节码】行号
00 09 表示【java源码】行号
9. 00 0B 表示引用了常量池#11项,是【LocalVariableTable】属性
00 00 00 0C 表示此属性的总长度,本例是12
00 01 表示【LocalVariableTable】长度
00 00 表示局部变量生命周期开始,相当于字节码的偏移量
00 05 表示局部变量覆盖的范围长度
00 0C 表示局部变量名称,本例引用了常量池#12项,是【this】
00 0D 表示局部变量的类型,本例引用了常量池#13项,是【Lpub/iyu/se/jvmday/day03/HelloWorld;】
00 00 表示局部变量占有的槽位(slot)编号,本例是0
1 | 00000200h: 00 00 05 00 0C 00 0D 00 00 00 09 00 0E 00 0F 00 |
- 00 09 代表访问修饰符(本类中是public static)
- 00 0E 代表引用了常量池#14项作为方法名称
- 00 0F 代表引用了常量池#15项作为方法参数描述
- 00 02 代表方法属性数量,本方法是2
- 方法属性1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2200 09 表示引用了常量池#09项,是【Code】属性
00 00 00 37 表示此属性的长度是55
00 02 表示【操作数栈】最大深度
00 01 表示【局部变量表】最大槽数(slot)
00 00 00 09 表示字节码长度,本例是9
B2 00 02 12 03 B6 00 04 B1 是字节码指令
00 00 00 02 表示方法细节属性数量,本例是2
00 0A 表示引用了常量池#10项,是【LineNumberTable】属性
00 00 00 0A 表示此属性的总长度,本例是10
00 02 表示【LineNumberTable】长度
00 00 表示【字节码】行号
00 0B 表示【java源码】行号
00 08 表示【字节码】行号
00 0C 表示【java源码】行号
00 0B 表示引用了常量池#11项,是【LocalVariableTable】属性
00 00 00 0C 表示此属性的总长度,本例是12
00 01 表示【LocalVariableTable】长度
00 00 表示局部变量生命周期开始,相当于字节码的偏移量
00 09 表示局部变量覆盖的范围长度
00 10 表示局部变量名称,本例引用了常量池#16项,是【args】
00 11 表示局部变量的类型,本例引用了常量池#17项,是【Ljava/lang/String;】
00 00 表示局部变量占有的槽位(slot)编号,本例是0 - 方法属性2:
1
2
3
4
500 12 表示引用了常量池#18项,发现是【MethodParameters】属性
00 00 00 05 表示此属性的总长度,本例是5
01 参数数量
00 10 表示引用了常量池#16项,是【args】
00 00 表示访问修饰符
1.7 附加属性
1 | u2 attributes_count; |
1 | 00000250h: 00 00 00 05 01 00 10 00 00 00 01 00 13 00 00 00 |
1 | 00 01 表示附加属性数量 |
参考资料:点击打开
2.字节码指令
2.1 分析
根据上面的分析,有两组字节码指令:
(1)public pub.iyu.se.jvmday.day03.HelloWorld(); 构造方法的字节码指令
2A B7 00 01 B1
1.2A => aload_0加载slot()的局部变量,即this,作为下面的invokespecial构造方法调用的参数
2.B7 => invokespecial预备调用构造方法,哪个方法呢?
3.00 01 => 引用常量池中#1项,即【Method java/lang/Object."<init>":()v】
4.B1 => 表示返回
(2)public static void main(java.lang.String[]) 主方法的字节码指令
B2 00 02 12 03 B6 00 04 B1
1.B2 => getstatic用来加载静态变量,哪个静态变量呢?
2.00 02 => 引用常量池中#2项,即【Field java/lang/System.out:Ljava/io/PrintStream;】
3.12 => ldc加载参数,哪个参数呢?
4.03 => 引用常量池中#3项,即【String Hello world】
5.B6 => invokevirtual预备调用成员方法,哪个方法呢?
6.00 04 => 引用常量池中#4项,即【Method java/io/PrintStream.println:(Ljava/lang/String;)V】
7.B1 => 表示返回
参考资料:点击打开
2.2 javap工具
Oracle 提供javap工具来反编译class文件
1 | javap -v HelloWorld.class |
结果如下:
1 | Classfile /E:/HelloWorld.class |
2.3 方法执行流程
(1) Demo代码如下:
1 | public class TestDemo0301 { |
(2)编译后的字节码文件 javap -v TestDemo0301.class
1 | Classfile /E:/TestDemo0301.class |
(3)常量池 载入 运行时常量池
(4)方法字节码载入方法区
(5)main线程开始运行,分配栈帧内存
stack=2(操作数栈), locals=4(局部变量表)
(6)执行引擎开始执行字节码
bipush 10
将一个byte压入操作数栈(其长度会补齐4个字节)
类似的指令还有:
1. sipush 将一个short压入操作数栈(其长度会补齐4个字节)
2. ldc将一个int压入操作数栈
3. ldc2_w将一个long压入操作数栈(分两次压入,因为long是8个字节)
这里小的数字都是和字节码指令存在一起,超过short范围的数字存入了常量池
istore 1
将操作数栈顶数据弹出,存入局部变量表的slot 1
ldc #3
从常量池加载#3数据到操作数栈
注意:Short.MAX_VALUE是32767,所以32768=Short.MAX_VALUE + 1 实际是在编译期间计算好的
istore 2
iload 1
iload 2
iadd
istore 3
getstatic #4
iload 3
invokevirtual #5
- 找到常量池#5项
- 定位到方法区 java/io/PrintStream.println:(I)V方法
- 生成新的栈帧(分配locals、stack等)
- 传递参数,执行新栈帧中的字节码
执行完毕,弹出栈帧
清除main操作数栈内容
return
完成main方法调用,弹出main栈帧,程序结束
2.4 分析 i++
从字节码的角度分析 i++ 相关问题
源码如下:
1 | public class TestDemo0302 { |
字节码:
1 | Classfile /E:/TestDemo0302.class |
分析:
- 注意iinc指令是直接在局部变量slot上进行运算
- a++ 和++a的区别是先执行iload还是先执行iinc
(1)a++ 先执行iload,再执行iinc
(2)++a 先执行iinc,再执行iload
2.5 条件判断指令
说明:
1. byte,short,char 都会按int比较,因为操作数栈都是4字节
2. goto 用来进行跳转到指定行号的字节码
源码:
1 | public static void main(String[] args) { |
字节码:
1 | Classfile /E:/TestDemo0303.class |
关于比较指令中 long float double 的比较,可以参考资料
2.6 循环控制指令
while循环:
1 | public static void main(String[] args) { |
字节码:
1 | Classfile /E:/TestDemo0304.class |
do while循环:
1 | public static void main(String[] args) { |
字节码:
1 | Classfile /E:/TestDemo0305.class |
2.7 判断结果
2.8 构造方法
2.9 方法调用
2.10 多态的原理
2.11 异常处理
2.12 finally
2.13 synchronized
3.编译期处理
4.类加载阶段
5.类加载器
6.运行期优化
未完待续。。。
评论加载中