JAVAP命令分析

一、关于javap命令

摘自官网:
javap可以反汇编一个或多个class文件。输出取决于所使用的选项。不使用任何选项时,该javap命令将打印public、protected 字段,以及method。
该javap命令不支持多版本JAR包。使用命令的类路径形式可以查看所有JAR文件(无论是否为multirelease)中的基本条目。使用URL形式,可以使用参数的URL形式来指定要反汇编的类的特定版本。
深入理解Java虚拟机栈的栈帧
JVM字节码之整型入栈指令(iconst、bipush、sipush、ldc)

javap命令可以做什么?

首先我们回顾一下为什么java语言可以跨平台:
java程序编译之后的代码,并不是能被硬件系统直接运行的代码,而是一种“中间码”——字节码(.class 文件),然后不同的硬件平台上安装有不同的Java虚拟机(JVM),由JVM来把字节码再“翻译”成所对应的硬件平台能够执行的代码。因此对于Java编程者来说,不需要考虑硬件平台是什么。所以Java可以跨平台。
也就是说由jvm虚拟机将class文件里面的字节码翻译成对应的硬件平台能够执行的代码。通俗一点就是class文件里面的字节码是jvm一系列的操作步骤,它会将这些步骤翻译给硬件系统去执行,比如创建一个对象,步骤大致为:
1、开辟空间
2、初始化
3、引用赋值
而javap命令,将这些步骤反汇编过来,使我们可以查看我们写的代码,jvm虚拟机是如何一步一步的去运行它的。

例如:

java源码:

public class LazySingleton {
    public static void main(String[] args) {
        LazySingleton lazySingleton = new LazySingleton();
    }
}

执行命令
javap -c -l LazySingleton
得到反汇编信息

public class com.test.design.singleton.LazySingleton {
//默认的构造方法,在构造方法执行时主要完成一些初始化操作,包括一些成员变量的初始化赋值等操作
  public com.test.design.singleton.LazySingleton();
    Code:
       0: aload_0 //从本地变量表中加载索引为0的变量的值,也就是this的引用,压入栈
       1: invokespecial #1 //出栈,调用java/lang/Object."<init>":()V 初始化对象,就是this指定的对象的init()方法完成初始化
       4: return //默认构造方法结束,返回
 //指令与代码行数的偏移对应关系,每一行第一个数字对应代码行数,第二个数字对应前面code中指令前面的数字
 LineNumberTable:
      line 3: 0
//局部变量表,start+length表示这个变量在字节码中的生命周期起始和结束的偏移位置(this生命周期从头0到结尾5),slot就是这个变量在局部变量表中的槽位(槽位可复用),name就是变量名称,Signatur局部变量类型描述
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
       0     5     0      this   Lcom/test/design/singleton/LazySingleton;
  public static void main(java.lang.String[]);
    Code:
    // new指令,创建一个class com/test/design/singleton/LazySingleton对象,new指令并不能完全创建一个对象,对象只有在调用初始化方法完成后(也就是调用了invokespecial指令之后),对象才创建成功
       0: new  #2  //class com/test/design/singleton/LazySingleton 创建对象,并将对象引用压入栈
       3: dup //将操作数栈定的数据复制一份,并压入栈,此时栈中有两个引用值,另外一个是this
       4: invokespecial #3  // Method "<init>":()V pop出栈引用值,调用其构造函数,完成对象的初始化
       7: astore_1 //pop出栈引用值,将其(引用)赋值给局部变量表中的变量lazySingleton, _1代码变量表中的slot(索引)
       8: return //方法结束,返回
LineNumberTable:
  line 5: 0
  line 6: 8
  //局部变量表,args是main方法的参数变量,
 LocalVariableTable:
  Start  Length  Slot  Name   Signature
 0       9     0  args   [Ljava/lang/String;
 8       1     1 lazySingleton  Lcom/test/design/singleton/LazySingleton;
}

i++ 与 ++i

public class LazySingleton {
    public static void main(String[] args) {
        int i = 0;
        i = i++;
        int j = 0;
        j = ++j;
        int k = 0;
        k = j++ + j++ ;
    }
}

反汇编分析

public class com.test.design.singleton.LazySingleton {
  public com.test.design.singleton.LazySingleton();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
    LineNumberTable:
      line 3: 0

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0 //    1、将常量0压入栈顶
       1: istore_1 //    2、把它赋值给第一个变量i
       2: iload_1 //     3、第一个变量i的值存入到栈顶
       3: iinc  1, 1  // 4、将第一个常量(i) 加一,而第3步存入到栈中的值没有变
       6: istore_1  //   5、将栈中的值赋给第一个常量i,从这里可以看出i++是将值压入栈顶,再执行自增操作,然后将栈中的值赋给自己。
       7: iconst_0 //    1、将常量0压入栈顶
       8: istore_2   //  2、把它赋值给第二个变量j
       9: iinc   2, 1 // 3、将第二个常量(j)加一,而第3步存入到栈中的值没有变
      12: iload_2     // 4、将第二个变量j的值存入到栈顶
      13: istore_2    // 5、将栈中的值再赋值给第二个常量j,从这里可以看出++j是自增,将变量j的值压入栈中,再将栈的值赋值自己。
      14: iconst_0    // 1、将常量0压入栈顶
      15: istore_3    // 2、把它赋值给第三个常量k
      16: iload_2	  // 3、将第二个常量j存入栈顶 (值为1)
      17: iinc 2, 1  //  4、将第二个常量j加一
      20: iload_2    //  5、将第二个常量j存入栈顶(值为2),此时栈中有0、1、2
      21: iinc  2, 1 //  6、将第二个常量j加一
      24: iadd	      // 7、将栈定存了常量的2个值弹出相加,再将结果存入栈中,此时栈中有0、1、2、3
      25: istore_3   //  8、将栈顶的值赋值给第3个常量k,
      26: return
    LineNumberTable:
      line 5: 0
      line 6: 2
      line 7: 7
      line 8: 9
      line 10: 14
      line 11: 16
      line 12: 26
}

结论:
i++ 先将值存入栈中,再运算,下一次使用变量时,再将变量的值压入栈中,再运算
++i 是先运算再将值压入栈中,

总结

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之前,常量池也在方法区)等部分,测试中没有显示出来,这里说明一下。
4、通过javap命令,分析代码的效率高低

在做值相关操作时:
一个指令,可以从局部变量表、常量池、堆中对象、方法调用、系统调用中等取得数据,这些数据(可能是指,可能是对象的引用)被压入操作数栈。
一个指令,也可以从操作数数栈中取出一到多个值(pop多次),完成赋值、加减乘除、方法传参、系统调用等等操作。

注意

关于指令,涉及到一个知识点,指令重排,jvm翻译给硬件系统的指令,并不是会按顺序执行,比如创建对象的指令:
1、开辟空间
2、初始化
3、引用赋值
cpu可能会对这些指令进行重排(指令重排),最终执行的指令可能是:
1、开辟空间
2、引用赋值
3、初始化
在单线程环境下不会有影响,多线程环境开发需要注意。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值