接卸字节码的作用
通过反编译生成的字节码文件,我们可以深入的了解Java代码的工作机制。但是,自己分析类文件结构太麻烦了!除了使用第三方的jclasslib工具之外,oracle官方也提供了工具:javap。
javap是jdk自带的反解析工具。它的作用就是根据class字节码文件,反解析出当前类对应的code区、局部变量表、异常表和代码行偏移量映射表、常量池等信息。
通过局部变量表,我们可以产看局部变量的作用域范围、所在槽位等信息,甚至可以看到槽位复用等信息。
javac -g 操作
解析字节码文件得到的信息中,有些信息(如局部变量表、指令和代码行偏移量映射表、常量池中方法的参数名称等等)需要在使用javac编译成class文件时,指定参数才能输出。
比如,直接javac xx.java,就不会生成对应的局部变量表等信息,如果你使用javac -g xx.java就可以生成所有相关信息了。如果你使用的eclipse或者IDEA,则默认情况下,eclipse、IDEA在编译时会帮你生成局部变量表、指令和代码行偏移量映射表等信息。
javap的用法
javap的用法格式:
javap <options> <classes>
其中,classes就是要反编译的class文件。 在命令行中直接输入javap或javap -help可以看到javap的options选项:
Usage: javap <options> <classes>
where possible options include:
-help --help -? Print this usage message
-version Version information
-v -verbose Print additional information
-l Print line number and local variable tables
-public Show only public classes and members
-protected Show protected/public classes and members
-package Show package/protected/public classes
and members (default)
-p -private Show all classes and members
-c Disassemble the code
-s Print internal type signatures
-sysinfo Show system info (path, size, date, MD5 hash)
of class being processed
-constants Show final constants
-classpath <path> Specify where to find user class files
-cp <path> Specify where to find user class files
-bootclasspath <path> Override location of bootstrap class files
使用举例
1.代码
package com.example.jvm;
public class SeniorDemo {
private int num = 1;
public final String info = "hello world";
boolean[] counts;
public SeniorDemo() {
}
public SeniorDemo(int count) {
this.counts = new boolean[count];
}
public String getInfo() {
return info;
}
public void addNum(int n) {
num += n;
System.out.println(num);
}
}
- javap -v SeniorDemo.class
Classfile /home/mall/work/gitrepository/jvm/out/production/jvm/SeniorDemo.class
Last modified Nov 11, 2021; size 852 bytes
MD5 checksum 0d32355daa6291e418aba33a4302a55d
Compiled from "SeniorDemo.java"
public class SeniorDemo // 类名称
minor version: 0
major version: 52 // 主版本号和副版本号组合,得出编译该文件的jdk版本
flags: ACC_PUBLIC, ACC_SUPER // 方法标识
Constant pool:
{
public final java.lang.String info;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC, ACC_FINAL
ConstantValue: String hello world
boolean[] counts;
descriptor: [Z
flags:
public SeniorDemo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1 // 操作数栈长度,局部变量表长度,参数个数
0: aload_0
1: invokespecial
4: aload_0
5: iconst_1
6: putfield
9: aload_0
10: ldc
12: putfield
15: return
LineNumberTable: // 行号表,代码行号与上面的code中的行号的对应
line 7: 0
line 3: 4
line 4: 9
line 9: 15
LocalVariableTable: // 局部变量表,start标识局部变量的开始作用域,与code中的相对于
Start Length Slot Name Signature
0 16 0 this LSeniorDemo; // 表明了作用域,变量名称,变量类型,也能看出槽位有没有被覆用的情况
public SeniorDemo(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: invokespecial
4: aload_0
5: iconst_1
6: putfield
9: aload_0
10: ldc
12: putfield
15: aload_0
16: iload_1
17: newarray boolean
19: putfield
22: return
LineNumberTable:
line 11: 0
line 3: 4
line 4: 9
line 12: 15
line 13: 22
LocalVariableTable:
Start Length Slot Name Signature
0 23 0 this LSeniorDemo;
0 23 1 count I
public java.lang.String getInfo();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: ldc
2: areturn
LineNumberTable:
line 16: 0
LocalVariableTable:
Start Length Slot Name Signature
0 3 0 this LSeniorDemo;
public void addNum(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=3, locals=2, args_size=2
0: aload_0
1: dup
2: getfield
5: iload_1
6: iadd
7: putfield
10: getstatic
13: aload_0
14: getfield
17: invokevirtual
20: return
LineNumberTable:
line 20: 0
line 21: 10
line 22: 20
LocalVariableTable:
Start Length Slot Name Signature
0 21 0 this LSeniorDemo;
0 21 1 n I
}
SourceFile: "SeniorDemo.java" // 表明类源文件的名称
总结
- 通过javap命令可以查看一共java类反汇编得到的Class文件版本号、常量池、访问标识、变量表、指令代码行号表等信息。不显式类索引、父类索引、接口类索引集合、<clinit>()、<init>等结构。
- 通过对前面例子代码反汇编文件的简单分析,可以发现,一个方法的执行通常会涉及下面几块内存的操作:
- java栈中,局部变量表、操作数栈
- java堆中,通过对象的地址引用去操作
- 常量池
- 其他如帧数据区、方法区的剩余部分等情况。
- 平时,我们比较关注的是java类中每个方法的反汇编中的指令操作过程,这些指令都是顺序执行的,可以参考官方文档查看每个指令的含义。
|