一、javap命令简述javap是jdk自带的反解析工具。它的作用就是根据class字节码文件,反解析出当前类对应的code区(汇编指令)、本地变量表、异常表和代码行偏移量映射表、常量池等等信息。 javap的用法格式: -help --help -? 输出此用法消息
-version 版本信息,其实是当前javap所在jdk的版本信息,不是class在哪个jdk下生成的。
-v -verbose 输出附加信息(包括行号、本地变量表,反汇编等详细信息)
-l 输出行号和本地变量表
-public 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-package 显示程序包/受保护的/公共类 和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示静态最终常量
-classpath <path> 指定查找用户类文件的位置
-bootclasspath <path> 覆盖引导类文件的位置
一般常用的是-v -l -c三个选项。 二、javap测试及内容详解前面已经介绍过javap输出的内容有哪些,东西比较多,这里主要介绍其中code区(汇编指令)、局部变量表和代码行偏移映射三个部分。 下面写段代码测试一下:
上面代码通过JAVAC -g 生成class文件,然后通过javap命令对字节码进行反汇编: Warning: Binary file TestDate contains com.justest.test.TestDate
Compiled from 'TestDate.java'
public class com.justest.test.TestDate {
//默认的构造方法,在构造方法执行时主要完成一些初始化操作,包括一些成员变量的初始化赋值等操作
public com.justest.test.TestDate();
Code:
0: aload_0 //从本地变量表中加载索引为0的变量的值,也即this的引用,压入栈
1: invokespecial #10 //出栈,调用java/lang/Object.'<init>':()V 初始化对象,就是this指定的对象的init()方法完成初始化
4: aload_0 // 4到6表示,调用this.count = 0,也即为count复制为0。这里this引用入栈
5: iconst_0 //将常量0,压入到操作数栈
6: putfield //出栈前面压入的两个值(this引用,常量值0), 将0取出,并赋值给count
9: return
//指令与代码行数的偏移对应关系,每一行第一个数字对应代码行数,第二个数字对应前面code中指令前面的数字
LineNumberTable:
line 5: 0
line 7: 4
line 5: 9
//局部变量表,start length表示这个变量在字节码中的生命周期起始和结束的偏移位置(this生命周期从头0到结尾10),slot就是这个变量在局部变量表中的槽位(槽位可复用),name就是变量名称,Signatur局部变量类型描述
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/justest/test/TestDate;
public static void main(java.lang.String[]);
Code:
// new指令,创建一个class com/justest/test/TestDate对象,new指令并不能完全创建一个对象,对象只有在初,只有在调用初始化方法完成后(也就是调用了invokespecial指令之后),对象才创建成功,
0: new //创建对象,并将对象引用压入栈
3: dup //将操作数栈定的数据复制一份,并压入栈,此时栈中有两个引用值
4: invokespecial #20 //pop出栈引用值,调用其构造函数,完成对象的初始化
7: astore_1 //pop出栈引用值,将其(引用)赋值给局部变量表中的变量testDate
8: aload_1 //将testDate的引用值压入栈,因为testDate.test1();调用了testDate,这里使用aload_1从局部变量表中获得对应的变量testDate的值并压入操作数栈
9: invokevirtual #21 // Method test1:()V 引用出栈,调用testDate的test1()方法
12: return //整个main方法结束返回
LineNumberTable:
line 10: 0
line 11: 8
line 12: 12
//局部变量表,testDate只有在创建完成并赋值后,才开始声明周期
LocalVariableTable:
Start Length Slot Name Signature
0 13 0 args [Ljava/lang/String;
8 5 1 testDate Lcom/justest/test/TestDate;
public void test1();
Code:
0: new #27 // 0到7创建Date对象,并赋值给date变量
3: dup
4: invokespecial #29 // Method java/util/Date.'<init>':()V
7: astore_1
8: ldc #30 // String wangerbei,将常量“wangerbei”压入栈
10: astore_2 //将栈中的“wangerbei”pop出,赋值给name1
11: aload_0 //11到14,对应test2(date,name1);默认前面加this.
12: aload_1 //从局部变量表中取出date变量
13: aload_2 //取出name1变量
14: invokevirtual #32 // Method test2: (Ljava/util/Date;Ljava/lang/String;)V 调用test2方法
// 17到38对应System.out.println(date name1);
17: getstatic #36 // Field java/lang/System.out:Ljava/io/PrintStream;
//20到35是jvm中的优化手段,多个字符串变量相加,不会两两创建一个字符串对象,而使用StringBuilder来创建一个对象
20: new #42 // class java/lang/StringBuilder
23: dup
24: invokespecial #44 // Method java/lang/StringBuilder.'<init>':()V
27: aload_1
28: invokevirtual #45 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
31: aload_2
32: invokevirtual #49 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
35: invokevirtual #52 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
38: invokevirtual #56 // Method java/io/PrintStream.println:(Ljava/lang/String;)V invokevirtual指令表示基于类调用方法
41: return
LineNumberTable:
line 15: 0
line 16: 8
line 17: 11
line 18: 17
line 19: 41
LocalVariableTable:
Start Length Slot Name Signature
0 42 0 this Lcom/justest/test/TestDate;
8 34 1 date Ljava/util/Date;
11 31 2 name1 Ljava/lang/String;
public void test2(java.util.Date, java.lang.String);
Code:
0: aconst_null //将一个null值压入栈
1: astore_1 //将null赋值给dateP
2: ldc #66 // String zhangsan 从常量池中取出字符串“zhangsan”压入栈中
4: astore_2 //将字符串赋值给name2
5: return
LineNumberTable:
line 22: 0
line 23: 2
line 24: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lcom/justest/test/TestDate;
0 6 1 dateP Ljava/util/Date;
0 6 2 name2 Ljava/lang/String;
public void test3();
Code:
0: aload_0 //取出this,压入栈
1: dup //复制操作数栈栈顶的值,并压入栈,此时有两个this对象引用值在操作数组栈
2: getfield #12// Field count:I this出栈,并获取其count字段,然后压入栈,此时栈中有一个this和一个count的值
5: iconst_1 //取出一个int常量1,压入操作数栈
6: iadd // 从栈中取出count和1,将count值和1相加,结果入栈
7: putfield #12 // Field count:I 一次弹出两个,第一个弹出的是上一步计算值,第二个弹出的this,将值赋值给this的count字段
10: return
LineNumberTable:
line 27: 0
line 28: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/justest/test/TestDate;
public void test4();
Code:
0: iconst_0
1: istore_1
2: iconst_0
3: istore_2
4: iload_1
5: iconst_1
6: iadd
7: istore_2
8: iload_1
9: iconst_1
10: iadd
11: istore_2
12: return
LineNumberTable:
line 33: 0
line 35: 2
line 36: 4
line 38: 8
line 39: 12
//看下面,b和c的槽位slot一样,这是因为b的作用域就在方法块中,方法块结束,局部变量表中的槽位就被释放,后面的变量就可以复用这个槽位
LocalVariableTable:
Start Length Slot Name Signature
0 13 0 this Lcom/justest/test/TestDate;
2 11 1 a I
4 4 2 b I
12 1 2 c I
}
例子2:下面一个例子
然后写一个操作User对象的测试类: public class TestUser {
private int count;
public void test(int a){
count = count a;
}
public User initUser(int age,String name){
User user = new User();
user.setAge(age);
user.setName(name);
return user;
}
public void changeUser(User user,String newName){
user.setName(newName);
}
}
先javac -g 编译成class文件。
三、总结1、通过javap命令可以查看一个java类反汇编、常量池、变量表、指令代码行号表等等信息。 2、平常,我们比较关注的是java类中每个方法的反汇编中的指令操作过程,这些指令都是顺序执行的,可以参考官方文档查看每个指令的含义,很简单: 3、通过对前面两个例子代码反汇编中各个指令操作的分析,可以发现,一个方法的执行通常会涉及下面几块内存的操作: (1)java栈中:局部变量表、操作数栈。这些操作基本上都值操作。 在做值相关操作时: |
|
来自: liang1234_ > 《好用的工具》