learn and grow up

class文件简单解析之二

字数统计: 1.7k阅读时长: 7 min
2020/07/12 Share

写在前面

最近闲暇时间在研究jvm虚拟机原理,所以把日常看到的、学习到的记录下来,这篇文章主要是讲基础的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
150
151
152
153
154
155
Classfile /Users/code/test/out/production/test/javp/JapTest.class
Last modified 2020-8-10; size 647 bytes
MD5 checksum 262a5f3739e7114c4154dde1717c57d6
Compiled from "JapTest.java"
public class javp.JapTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
//ACC_PUBLIC 0x0001 可以被包的类外访问。
//ACC_SUPER 0x0020 当用到invokespecial指令时,需要特殊处理的父类方法。
//invokespecial只能调用三类方法:<init>方法;private方法;super.method()。因为这三类方法的调用对象在编译时就可以确定。
//invokevirtual是一种动态分派的调用指令:也就是引用的类型并不能决定方法属于哪个类型。
Constant pool://常量池声明,拼接的注释是编译器自己注释的
//格式说明:索引 = tag/类型 值
#1 = Methodref #6.#26 // java/lang/Object."<init>":()V
#2 = String #27 // 2222
#3 = Fieldref #28.#29 // java/lang/System.out:Ljava/io/PrintStream;
#4 = Methodref #30.#31 // java/io/PrintStream.println:(I)V
#5 = Class #32 // javp/JapTest
#6 = Class #33 // java/lang/Object
#7 = Utf8 <init>
/** 1.java在编译之后会在字节码文件中生成<init>方法,称之为实例构造器,该实例构造器会将语句块、变量初始化、调用父类的构造器等操作收敛到<init>方法中,收敛顺序为:
1.父类变量初始化块 2.父类语句块 3.父类构造函数 4.子类变量初始化块 5.子类语句块 6.子类构造函数
所谓收敛到<init>方法中的意思是:将这些操作放入到<init>中去执行。

java在编译之后会在字节码文件中生成<clinit>方法,称之为类构造器,类构造器同实例构造器一样,也会将静态语句块、静态变量初始化,收敛到<clinit>方法中,收敛顺序为 1.父类静态变量初始化 2.父类静态语句块 3.子类静态变量初始化 4.子类静态语句块。 若父类为接口,则不会调用父类的clinit方法。一个类可以没有clinit方法。
<clinit>方法是在类加载过程中执行的,而<init>是在对象实例化执行的,所以<clinit>一定比<init>先执行。所以整个顺序为: 1.父类静态变量初始化 2.父类静态语句块 3.子类静态变量初始化 4.子类静态语句块 5.父类变量初始化块 6.父类语句块 7.父类构造函数 8.子类变量初始化块 9.子类语句块 10.子类构造函数
**/

#8 = Utf8 ()V//init的方法签名,返会void,参数为空
#9 = Utf8 Code //CONSTANT_Utf8
#10 = Utf8 LineNumberTable//
#11 = Utf8 LocalVariableTable//
#12 = Utf8 this//
#13 = Utf8 Ljavp/JapTest;//
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 c
#17 = Utf8 I
#18 = Utf8 args
#19 = Utf8 [Ljava/lang/String;
#20 = Utf8 i
#21 = Utf8 Ljava/lang/String;
#22 = Utf8 StackMapTable
#23 = Class #34 // java/lang/String
#24 = Utf8 SourceFile
#25 = Utf8 JapTest.java
#26 = NameAndType #7:#8 // "<init>":()V
#27 = Utf8 2222
#28 = Class #35 // java/lang/System
#29 = NameAndType #36:#37 // out:Ljava/io/PrintStream;
#30 = Class #38 // java/io/PrintStream
#31 = NameAndType #39:#40 // println:(I)V
#32 = Utf8 javp/JapTest
#33 = Utf8 java/lang/Object
#34 = Utf8 java/lang/String
#35 = Utf8 java/lang/System
#36 = Utf8 out
#37 = Utf8 Ljava/io/PrintStream;
#38 = Utf8 java/io/PrintStream
#39 = Utf8 println
#40 = Utf8 (I)V
{
public javp.JapTest();//默认的构造函数
descriptor: ()V//方法签名
flags: ACC_PUBLIC//public
Code: //方法的字节码
//当前方法的操作数栈,前方法引用的局部变量表中的局部变量个数,参数个数
stack=1, locals=1, args_size=1
//实现当前方法的Java虚拟机字节码
//加载第一个参数
//注意:在非静态方法中,aload_0 表示对this的操作,在static 方法中,aload_0表示对方法的第一参数的操作。所以这里指的是this
0: aload_0
//调用父类的方法
1: invokespecial #1 // Method java/lang/Object."<init>":()V
//return
4: return
//LineNumberTable:描述Java源代码行号与字节码行号(字节码偏移量)之间的对应关系
LineNumberTable:
//源码中的行数:字节码行数
line 3: 0
//LocalVariableTable:描述栈帧中局部变量表中的变量与字节码code中定义的变量及源码之间的关系
LocalVariableTable:
//作用开始的字节码偏移量 作用覆盖长度 名称 占用的域 签名
//一个slot就是JVM规范对局部变量区里存储一个局部变量的存储单元的叫法。每个slot可以存储一个宽度在32位或以下的原始类型值,或者一个引用,或一个returnAddress;相邻的两个slot可存储一个double或long型的值。
//关于slot更加清晰的解析:https://www.cnblogs.com/FlyAway2013/p/10476021.html
Start Length Slot Name Signature
0 5 0 this Ljavp/JapTest;
//第二个方法,也就是源码里的main方法
public static void main(java.lang.String[]);
//签名:参数为String数组,返回为void
descriptor: ([Ljava/lang/String;)V
//public static
flags: ACC_PUBLIC, ACC_STATIC
Code://字节码
//当前方法的操作数栈,前方法引用的局部变量表中的局部变量个数,参数个数
stack=2, locals=3, args_size=1
//LoaD Constant #2
0: ldc #2 // String 2222
//存到第1个局部变量,也就是 i
2: astore_1
//定义int类型值为1的常量
3: iconst_1
//存到第2个局部变量,也就是 c
4: istore_2
//加载第2个局部变量到操作栈,c:1
5: iload_2
//push20到操作栈
6: bipush 20
//对比两个int,如果成立则往下执行,否则执行24行code
8: if_icmpge 24
//加载静态类
11: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
//load第二个变量,类型为int,也就是c
14: iload_2
//调用静态方法
15: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
//对第2个变量进行加1操作,也就是对c加1
18: iinc 2, 1
//goto第五行继续判断
21: goto 5
//return
24: return
LineNumberTable:
//源码中的行数:字节码行数

line 5: 0
line 6: 3
line 7: 11
line 6: 18
line 9: 24
LocalVariableTable:
//LocalVariableTable:描述栈帧中局部变量表中的变量与字节码code中定义的变量及源码之间的关系
Start Length Slot Name Signature
//变量c,作用范围为字节码第5行到第19行,占用1个slot,slot位置为2.
5 19 2 c I
//....
0 25 0 args [Ljava/lang/String;
//....
3 22 1 i Ljava/lang/String;
//StackMapTable比较隐晦,我的理解是StackMapTable记录的是一个方法中操作数栈与局部变量区的类型在一些特定位置的状态,更具体的参考大牛的回答:https://hllvm-group.iteye.com/group/topic/26545
//对应到如下可以看到有两个StackMapTable,number_of_entries=2(第一个是隐式的。因为除了this,没有load其他变量,所以locals=[])
StackMapTable: number_of_entries = 2
// append的StackMapTable
frame_type = 253 /* append */
//开始的code偏移量为5
offset_delta = 5
//局部变量去为String、int
locals = [ class java/lang/String, int ]
frame_type = 250 /* chop */
//空局部变量
//code偏移量为18
offset_delta = 18
}
SourceFile: "JapTest.java"
CATALOG
  1. 1. 写在前面
  2. 2. 实例