javap解析Class文件

1、解析字节码文件作用

  • 通过反编译生成的字节码文件,我们可以深入的了解java代码的工作机制。但是,自己分析类文件结构太麻烦了!除了使用第三方的jclasslib工具之外,oracle官方也提供了工具:javap。

  • javap是jdk自带的反解析工具。它的作用就是根据c1ass字节码文件,反解析出当前类对应的code区(字节码指令)、局部变量表、异常表和代码行偏移量映射表、常量池等信息。

  • 通过局部变量表,我们可以查看局部变量的作用域范围、所在槽位等信息,甚至可以看到槽位复用等信息。

  • 下面以JavapTest.java为例子进行说明:

    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
    public class JavapTest {
    private int num;
    boolean flag;
    protected char gender;
    public String info;

    public static final int COUNTS = 1;
    static{
    String url = "perfectcode.top";
    }
    {
    info = "java";
    }
    public JavapTest(){

    }
    private JavapTest(boolean flag){
    this.flag = flag;
    }
    private void methodPrivate(){

    }
    int getNum(int i){
    return num + i;
    }
    protected char showGender(){
    return gender;
    }
    public void showInfo(){
    int i = 10;
    System.out.println(info + i);
    }
    }

2、javac -g操作

  • 解析字节码文件得到的信息中,有些信息(如局部变量表、指令和代码行偏移量映射表、常量池中方法的参数名称等等)需要在使用javac编译成class文件时,指定参数才能输出。
    • 比如,你直接javac xx.java,就不会在生成对应的局部变量表等信息,如果你使用javac -g xx.java就可以生成所有相关信息了。如果你使用的eclipse或IDEA,则默认情况下,eclipse、IDEA在编译时会帮你生成局部变量表、指令和代码行偏移量映射表等信息的。
  • 进入到JavapTest.java的根目录下,运行命令javac -g JavapTest.java后,会自动在同个目录下生成对应的字节码文件JavapTest.class。

3、javap用法

  • javap的用法格式:javap ,其中,classes就是你要反编译的class文件。

  • 在命令行中直接输入javap或javap -help可以看到javap的options有如下选项:

  • javap -version:版本信息,其实是当前javap所在jdk的版本信息,不是class在哪个jdk下生成的。

  • javap -public:仅显示公共类和成员。

  • javap -protected:显示受保护的/公共类和成员。

  • javap -p/-private:显示所有类和成员。

  • javap -package:显示程序包/受保护的/公共类和成员(默认)。

  • javap -sysinfo:显示正在处的类的系统信息(路径,大小,日期,MD5散列,源文件名)。

  • javap -constants:显示静态最终常量。

  • javap -s:输出内部类型签名。

  • javap -l:输出行号和本地变量表(也叫局部变量表)。

  • javap -c:对代码进行反汇编。

  • javap -v/-verbose:输出附加信息(包括行号、本地变量表,反汇编等详细信息)。

    虽然信息很全面,但是不包括私有结构的信息,所以可以使用javap -v -p指令。

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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
Classfile /C:/Users/wo/Desktop/JavapTest.class //字节码文件所属的路径
Last modified 2021-1-22; size 1360 bytes //最后修改时间;字节码文件的大小
MD5 checksum ab77a75c68a04dee24971e971af5b696 //MD5散列值
Compiled from "JavapTest.java" //源文件的名称
public class com.company.java12.JavapTest
minor version: 0 //副版本
major version: 52 //主版本
flags: ACC_PUBLIC, ACC_SUPER //访问标识
Constant pool: //常量池信息
#1 = Methodref #16.#46 // java/lang/Object."<init>":()V
#2 = String #47 // java
#3 = Fieldref #15.#48 // com/company/java12/JavapTest.info:Ljava/lang/String;
#4 = Fieldref #15.#49 // com/company/java12/JavapTest.flag:Z
#5 = Fieldref #15.#50 // com/company/java12/JavapTest.num:I
#6 = Fieldref #15.#51 // com/company/java12/JavapTest.gender:C
#7 = Fieldref #52.#53 // java/lang/System.out:Ljava/io/PrintStream;
#8 = Class #54 // java/lang/StringBuilder
#9 = Methodref #8.#46 // java/lang/StringBuilder."<init>":()V
#10 = Methodref #8.#55 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#11 = Methodref #8.#56 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
#12 = Methodref #8.#57 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#13 = Methodref #58.#59 // java/io/PrintStream.println:(Ljava/lang/String;)V
#14 = String #60 // perfectcode.top
#15 = Class #61 // com/company/java12/JavapTest
#16 = Class #62 // java/lang/Object
#17 = Utf8 num
#18 = Utf8 I
#19 = Utf8 flag
#20 = Utf8 Z
#21 = Utf8 gender
#22 = Utf8 C
#23 = Utf8 info
#24 = Utf8 Ljava/lang/String;
#25 = Utf8 COUNTS
#26 = Utf8 ConstantValue
#27 = Integer 1
#28 = Utf8 <init>
#29 = Utf8 ()V
#30 = Utf8 Code
#31 = Utf8 LineNumberTable
#32 = Utf8 LocalVariableTable
#33 = Utf8 this
#34 = Utf8 Lcom/company/java12/JavapTest;
#35 = Utf8 (Z)V
#36 = Utf8 methodPrivate
#37 = Utf8 getNum
#38 = Utf8 (I)I
#39 = Utf8 i
#40 = Utf8 showGender
#41 = Utf8 ()C
#42 = Utf8 showInfo
#43 = Utf8 <clinit>
#44 = Utf8 SourceFile
#45 = Utf8 JavapTest.java
#46 = NameAndType #28:#29 // "<init>":()V
#47 = Utf8 java
#48 = NameAndType #23:#24 // info:Ljava/lang/String;
#49 = NameAndType #19:#20 // flag:Z
#50 = NameAndType #17:#18 // num:I
#51 = NameAndType #21:#22 // gender:C
#52 = Class #63 // java/lang/System
#53 = NameAndType #64:#65 // out:Ljava/io/PrintStream;
#54 = Utf8 java/lang/StringBuilder
#55 = NameAndType #66:#67 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#56 = NameAndType #66:#68 // append:(I)Ljava/lang/StringBuilder;
#57 = NameAndType #69:#70 // toString:()Ljava/lang/String;
#58 = Class #71 // java/io/PrintStream
#59 = NameAndType #72:#73 // println:(Ljava/lang/String;)V
#60 = Utf8 perfectcode.top
#61 = Utf8 com/company/java12/JavapTest
#62 = Utf8 java/lang/Object
#63 = Utf8 java/lang/System
#64 = Utf8 out
#65 = Utf8 Ljava/io/PrintStream;
#66 = Utf8 append
#67 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#68 = Utf8 (I)Ljava/lang/StringBuilder;
#69 = Utf8 toString
#70 = Utf8 ()Ljava/lang/String;
#71 = Utf8 java/io/PrintStream
#72 = Utf8 println
#73 = Utf8 (Ljava/lang/String;)V
{ //字段表集合的信息
private int num; //字段名
descriptor: I //字段描述符:字段的类型
flags: ACC_PRIVATE //字段的访问标识

boolean flag;
descriptor: Z
flags:

protected char gender;
descriptor: C
flags: ACC_PROTECTED

public java.lang.String info;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC

public static final int COUNTS;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 1 //常量字段的属性ConstantValue
//方法表集合的信息
public com.company.java12.JavapTest(); //构造器1的信息
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: ldc #2 // String java
7: putfield #3 // Field info:Ljava/lang/String;
10: return
LineNumberTable:
line 21: 0
line 19: 4
line 23: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/company/java12/JavapTest;

private com.company.java12.JavapTest(boolean); //构造器2的信息
descriptor: (Z)V
flags: ACC_PRIVATE
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #2 // String java
7: putfield #3 // Field info:Ljava/lang/String;
10: aload_0
11: iload_1
12: putfield #4 // Field flag:Z
15: return
LineNumberTable:
line 24: 0
line 19: 4
line 25: 10
line 26: 15
LocalVariableTable:
Start Length Slot Name Signature
0 16 0 this Lcom/company/java12/JavapTest;
0 16 1 flag Z

private void methodPrivate();
descriptor: ()V
flags: ACC_PRIVATE
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 29: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcom/company/java12/JavapTest;

int getNum(int);
descriptor: (I)I
flags:
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: getfield #5 // Field num:I
4: iload_1
5: iadd
6: ireturn
LineNumberTable:
line 31: 0
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 this Lcom/company/java12/JavapTest;
0 7 1 i I

protected char showGender();
descriptor: ()C
flags: ACC_PROTECTED
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #6 // Field gender:C
4: ireturn
LineNumberTable:
line 34: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/company/java12/JavapTest;

public void showInfo();
descriptor: ()V //方法描述符:方法的形参列表、返回值类型
flags: ACC_PUBLIC //方法的访问标识
Code: //方法的Code属性
stack=3, locals=2, args_size=1 //stack:操作数栈的最大深度;locals:局部变量表的最大长度;args_size:方法接收参数的个数
//偏移量:操作码 操作数
0: bipush 10
2: istore_1
3: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
6: new #8 // class java/lang/StringBuilder
9: dup
10: invokespecial #9 // Method java/lang/StringBuilder."<init>":()V
13: aload_0
14: getfield #3 // Field info:Ljava/lang/String;
17: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: iload_1
21: invokevirtual #11 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
24: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: return
//行号表:指明字节码指令的偏移量和java源程序中代码的一一对应关系
LineNumberTable:
line 37: 0
line 38: 3
line 39: 30
//局部变量表:描述内部局部变量的相关信息
LocalVariableTable:
Start Length Slot Name Signature
0 31 0 this Lcom/company/java12/JavapTest;
3 28 1 i I

static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=1, args_size=0
0: ldc #14 // String perfectcode.top
2: astore_0
3: return
LineNumberTable:
line 16: 0
line 17: 3
LocalVariableTable:
Start Length Slot Name Signature
}
SourceFile: "JavapTest.java" //附加属性:指明当前字节码文件对应的源程序文件名

4、总结

  • 通过javap命令可以查看一个java类反汇编得到的lass文件版本号、常量池、访问标识、变量表、指令代码行号表等等信息。不显示类索引、父类索引、接口索引集合、()、()等结构。
  • 通过对前面例子代码反汇编文件的简单分析,可以发现,一个方法的执行通常会涉及下面几块内存的操作:
    • java栈中:局部变量表、操作数栈。
    • java堆。通过对象的地址引用去操作。
    • 常量池。
    • 其他如帧数据区(栈帧中除了局部变量表和操作数栈以外的信息)、方法区的剩余部分等情况,测试中没有显示出来,这里说明一下。