【JVM】通过javap命令分析Java汇编指令
文章目录
- javap命令简述
- javap测试及内容详解
- 例子1
- 例子2
- 总结
- 转载说明
javap命令简述
javap是jdk自带的反解析工具。它的作用就是根据class字节码文件,反解析出当前类对应的code区(汇编指令)、本地变量表、异常表和代码行偏移量映射表、常量池等等信息。
当然这些信息中,有些信息(如本地变量表、指令和代码行偏移量映射表、常量池中方法的参数名称等等)需要在使用javac编译成class文件时,指定参数才能输出,比如,你直接javac xx.java,就不会在生成对应的局部变量表等信息,如果你使用javac -g xx.java就可以生成所有相关信息了。如果你使用的eclipse,则默认情况下,eclipse在编译时会帮你生成局部变量表、指令和代码行偏移量映射表等信息的。
通过反编译生成的汇编代码,我们可以深入的了解java代码的工作机制。比如我们可以查看i++;这行代码实际运行时是先获取变量i的值,然后将这个值加1,最后再将加1后的值赋值给变量i。
通过局部变量表,我们可以查看局部变量的作用域范围、所在槽位等信息,甚至可以看到槽位复用等信息。
javap的用法格式:
javap <options> <classes>其中classes就是你要反编译的class文件。
在命令行中直接输入javap或javap -help可以看到javap的options有如下选项:
一般常用的是-v -l -c三个选项。
javap -v classxx,不仅会输出行号、本地变量表信息、反编译汇编代码,还会输出当前类用到的常量池等信息。
javap -l 会输出行号和本地变量表信息。
javap -c 会对当前class字节码进行反编译生成汇编代码。
查看汇编代码时,需要知道里面的jvm指令,可以参考官方文档:
https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html
另外通过jclasslib工具也可以看到上面这些信息,而且是可视化的,效果更好一些。
javap测试及内容详解
前面已经介绍过javap输出的内容有哪些,东西比较多,这里主要介绍其中code区(汇编指令)、局部变量表和代码行偏移映射三个部分。
如果需要分析更多的信息,可以使用javap -v进行查看。
另外,为了更方便理解,所有汇编指令不单拎出来讲解,而是在反汇编代码中以注释的方式讲解。
下面写段代码测试一下:
例子1
分析一下下面的代码反汇编之后结果:
public class TestDate {private int count = 0;public static void main(String[] args) {TestDate testDate = new TestDate();testDate.test1();}public void test1(){Date date = new Date();String name1 = "wangerbei";test2(date,name1); System.out.println(date+name1);}public void test2(Date dateP,String name2){dateP = null;name2 = "zhangsan";}public void test3(){count++;}public void test4(){int a = 0;{int b = 0;b = a+1;}int c = a+1;} }上面代码通过javac -g 生成class文件,然后通过javap命令对字节码进行反汇编:
$ javap -c -l TestDate
得到下面内容(指令等部分是我参照着官方文档总结的):
例子2
下面一个例子
先有一个User类:
然后写一个操作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文件。
然后对TestUser类进行反汇编:
得到反汇编结果如下:
Warning: Binary file TestUser contains com.justest.test.TestUser Compiled from "TestUser.java"public class com.justest.test.TestUser {//默认的构造函数public com.justest.test.TestUser();Code:0: aload_01: invokespecial #10 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 3: 0LocalVariableTable:Start Length Slot Name Signature0 5 0 this Lcom/justest/test/TestUser;public void test(int);Code:0: aload_0 //取this对应的对应引用值,压入操作数栈1: dup //复制栈顶的数据,压入栈,此时栈中有两个值,都是this对象引用2: getfield #18 // 引用出栈,通过引用获得对应count的值,并压入栈5: iload_1 //从局部变量表中取得a的值,压入栈中6: iadd //弹出栈中的count值和a的值,进行加操作,并将结果压入栈7: putfield #18 // 经过上一步操作后,栈中有两个值,栈顶为上一步操作结果,栈顶下面是this引用,这一步putfield指令,用于将栈顶的值赋值给引用对象的count字段10: return //return voidLineNumberTable:line 8: 0line 9: 10LocalVariableTable:Start Length Slot Name Signature0 11 0 this Lcom/justest/test/TestUser;0 11 1 a Ipublic com.justest.test.User initUser(int, java.lang.String);Code:0: new #23 // class com/justest/test/User 创建User对象,并将引用压入栈3: dup //复制栈顶值,再次压入栈,栈中有两个User对象的地址引用4: invokespecial #25 // Method com/justest/test/User."<init>":()V 调用user对象初始化7: astore_3 //从栈中pop出User对象的引用值,并赋值给局部变量表中user变量8: aload_3 //从局部变量表中获得user的值,也就是User对象的地址引用,压入栈中9: iload_1 //从局部变量表中获得a的值,并压入栈中,注意aload和iload的区别,一个取值是对象引用,一个是取int类型数据10: invokevirtual #26 // Method com/justest/test/User.setAge:(I)V 操作数栈pop出两个值,一个是User对象引用,一个是a的值,调用setAge方法,并将a的值传给这个方法,setAge操作的就是堆中对象的字段了13: aload_3 //同7,压入栈14: aload_2 //从局部变量表取出name,压入栈15: invokevirtual #29 // MethodUser.setName:(Ljava/lang/String;)V 操作数栈pop出两个值,一个是User对象引用,一个是name的值,调用setName方法,并将a的值传给这个方法,setName操作的就是堆中对象的字段了18: aload_3 //从局部变量取出User引用,压入栈19: areturn //areturn指令用于返回一个对象的引用,也就是上一步中User的引用,这个返回值将会被压入调用当前方法的那个方法的栈中objectref is popped from the operand stack of the current frame ([§2.6](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.6)) and pushed onto the operand stack of the frame of the invokerLineNumberTable:line 12: 0line 13: 8line 14: 13line 15: 18LocalVariableTable:Start Length Slot Name Signature0 20 0 this Lcom/justest/test/TestUser;0 20 1 age I0 20 2 name Ljava/lang/String;8 12 3 user Lcom/justest/test/User;public void changeUser(com.justest.test.User, java.lang.String);Code:0: aload_1 //局部变量表中取出user,也即User对象引用,压入栈1: aload_2 //局部变量表中取出newName,压入栈2: invokevirtual #29 // Method User.setName:(Ljava/lang/String;)V pop出栈newName值和TestUser引用,调用其setName方法,并将newName的值传给这个方法5: returnLineNumberTable:line 19: 0line 20: 5LocalVariableTable:Start Length Slot Name Signature0 6 0 this Lcom/justest/test/TestUser;0 6 1 user Lcom/justest/test/User;0 6 2 newName Ljava/lang/String;public static void main(java.lang.String[]);Code:0: new #1 // class com/justest/test/TestUser 创建TestUser对象,将引用压入栈3: dup //复制引用,压入栈4: invokespecial #43 // Method "<init>":()V 引用值出栈,调用构造方法,对象初始化7: astore_1 //引用值出栈,赋值给局部变量表中变量tu8: aload_1 //取出tu值,压入栈9: bipush 10 //将int值10压入栈11: ldc #44 // String wangerbei 从常量池中取出“wangerbei” 压入栈13: invokevirtual #46 // Method initUser(ILjava/lang/String;)Lcom/justest/test/User; 调用tu的initUser方法,并返回User对象 ,出栈三个值:tu引用,10和“wangerbei”,并且initUser方法的返回值,即User的引用,也会被压入栈中,参考前面initUser中的areturn指令16: astore_2 //User引用出栈,赋值给user变量17: aload_1 //取出tu值,压入栈18: aload_2 //取出user值,压入栈19: ldc #48 // String lisi 从常量池中取出“lisi”压入栈21: invokevirtual #50 // Method changeUser:(Lcom/justest/test/User;Ljava/lang/String;)V 调用tu的changeUser方法,并将user引用和lisi传给这个方法24: return //return voidLineNumberTable:line 23: 0line 24: 8line 25: 17line 26: 24LocalVariableTable:Start Length Slot Name Signature0 25 0 args [Ljava/lang/String;8 17 1 tu Lcom/justest/test/TestUser;17 8 2 user Lcom/justest/test/User;}总结
1、通过javap命令可以查看一个java类反汇编、常量池、变量表、指令代码行号表等等信息。
2、平常,我们比较关注的是java类中每个方法的反汇编中的指令操作过程,这些指令都是顺序执行的,可以参考官方文档查看每个指令的含义,很简单:
https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.areturn
3、通过对前面两个例子代码反汇编中各个指令操作的分析,可以发现,一个方法的执行通常会涉及下面几块内存的操作:
(1)java栈中:局部变量表、操作数栈。这些操作基本上都值操作。
(2)java堆。通过对象的地址引用去操作。
(3)常量池。
(4)其他如帧数据区、方法区(jdk1.8之前,常量池也在方法区)等部分,测试中没有显示出来,这里说明一下。
在做值相关操作时:
一个指令,可以从局部变量表、常量池、堆中对象、方法调用、系统调用中等取得数据,这些数据(可能是指,可能是对象的引用)被压入操作数栈。
一个指令,也可以从操作数数栈中取出一到多个值(pop多次),完成赋值、加减乘除、方法传参、系统调用等等操作。
转载说明
原文作者:王二北
原文链接:https://www.jianshu.com/p/6a8997560b05
原文来源:简书
转载用途:学习、分享、非商业用途
转载理由:详细讲解如何使用javap命令分析JVM汇编指令,很有学习价值。
总结
以上是生活随笔为你收集整理的【JVM】通过javap命令分析Java汇编指令的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: 【计算机科学基础】浅析二进制“怪异数”
- 下一篇: 蜜蜂路线(洛谷P2437题题解,Java