javap命令的了解--从事例了解开始

javap是jdk自带的反编译工具。这个工具是反编译java的.class文件,经过反编译文件后,你可以很清楚的看到程序的流程,每一步做了什么,反编译的文件可以很好的带你了解java代码的工作机制。

例如:我们我们经常使用 i++;这行代码实际运行时是先获取变量i的值,然后将这个值加1,最后再将加1后的值赋值给变量i。这只是我们知道了他整体的步骤哦,但是底层我们不是特别的了解,我们可以通过javap这个命令,进行反编译,看出这个i++真实底层的样子。

例子: Test2.java

public class Test2{

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

  编译后的文件:Test2.class(这个文件很生涩,很难看懂,一般人看过去都会懵逼,例如我这个一般人)

cafe babe 0000 0032 000f 0a00 0300 0c07
000d 0700 0e01 0006 3c69 6e69 743e 0100
0328 2956 0100 0443 6f64 6501 000f 4c69
6e65 4e75 6d62 6572 5461 626c 6501 0004
6d61 696e 0100 1628 5b4c 6a61 7661 2f6c
616e 672f 5374 7269 6e67 3b29 5601 000a
536f 7572 6365 4669 6c65 0100 0a54 6573
7432 2e6a 6176 610c 0004 0005 0100 0554
6573 7432 0100 106a 6176 612f 6c61 6e67
2f4f 626a 6563 7400 2100 0200 0300 0000
0000 0200 0100 0400 0500 0100 0600 0000
1d00 0100 0100 0000 052a b700 01b1 0000
0001 0007 0000 0006 0001 0000 0002 0009
0008 0009 0001 0006 0000 0026 0001 0002
0000 0006 033c 8401 01b1 0000 0001 0007
0000 000e 0003 0000 0005 0002 0006 0005
0007 0001 000a 0000 0002 000b 

经过javap反编译的文件:(javap -c Test2 >> test2.txt    我这边是写入到了一个test2.txt文件里面,比较操作)

Compiled from "Test2.java"
public class Test2 extends java.lang.Object{
public Test2();
  Code:
   0:	aload_0
   1:	invokespecial	#1; //Method java/lang/Object."<init>":()V
   4:	return

public static void main(java.lang.String[]);
  Code: 
   0:	iconst_0         # iconst_0指令将常量0压入栈中
   1:	istore_1         # istore_1 从栈中取出常量0存储到局部变量表中,下标索引为1
   2:	iinc	1, 1     # 将局部变量表中下标索引为1的变量自增
   5:	return           # 将值返回 i=1

}

可以看看反编译的步骤,我做了备注,你们可以看看。

下面我们说一下javap的语法 :javap <options> <classes>

options 可以选择的值:

   -c                        对代码进行反汇编
   -classpath <pathlist>     指定查找用户类文件的位置
   -extdirs <dirs>           Override location of installed extensions
   -help                     Print this usage message
   -J<flag>                  Pass <flag> directly to the runtime system
   -l                        打印行号和本地变量表
   -public                   仅显示公共类和成员
   -protected                显示受保护的/公共类和成员
   -package                  显示程序包/受保护的/公共类和成员 (默认)
   -private                  显示程序包/私有成员
   -s                        输出内部类型签名
   -bootclasspath <pathlist> 覆盖引导类文件的位置
   -verbose                  输出附加信息(包括行号、本地变量表,反汇编等详细信息)

一般常用的是-v -l -c三个选项。
javap -v classxx,不仅会输出行号、本地变量表信息、反编译汇编代码,还会输出当前类用到的常量池等信息。
javap -l 会输出行号和本地变量表信息。
javap -c 会对当前class字节码进行反编译生成汇编代码。
查看汇编代码时,需要知道里面的jvm指令,可以参考官方文档:
https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html

我们可以通过官方文档反编译的对应的关键字的含义:如iconst、bipush、sipush、ldc、istore_<n>等关键字含义:我们看个再看个例子:

VM字节码之整型入栈指令(iconst、bipush、sipush、ldc)
JVM中int类型数值采用何种指令入栈的,根据int值范围JVM入栈字节码指令就分为4类,下面分别介绍下这四类指令。

当int取值-1~5采用iconst指令,取值-128~127采用bipush指令,取值-32768~32767采用sipush指令,取值-2147483648~2147483647采用 ldc 指令。

iconst
当int取值-1~5时,JVM采用iconst指令将常量压入栈中。
定义Test.java文件

public static void main(String[] args) {
    int i = 5;
    int j = -1;
}

反编译class的文件

public static void main(java.lang.String[]);
  Code:
   0:   iconst_5
   1:   istore_1
   2:   iconst_m1
   3:   istore_2
   4:   return
}

分析class文件,int取值0~5时JVM采用iconst_0、iconst_1、iconst_2、iconst_3、iconst_4、iconst_5指令将常量压入栈中,取值-1时采用iconst_m1指令将常量压入栈中。

bipush

当int取值-128~127时,JVM采用bipush指令将常量压入栈中。
定义Test.java文件

public static void main(String[] args) {
     int i = 127;
}

反编译class的文件

public static void main(java.lang.String[]);
     Code:
       0:   bipush  127
       2:   istore_1
       3:   return
}

可以看到上面代码第三行是采用bipush指令将常量127压入栈中。

sipush

当int取值-32768~32767时,JVM采用sipush指令将常量压入栈中。
定义Test.java

public static void main(String[] args) {
     int i = 32767;
}

反编译class的文件

public static void main(java.lang.String[]);
  Code:
   0:   sipush  32767
   3:   istore_1
   4:   return
}

可以看到上面代码第三行是采用sipush指令将常量32767压入栈中。

ldc

当int取值-2147483648~2147483647时,JVM采用ldc指令将常量压入栈中。
定义Test.java文件

public static void main(String[] args) {
    int i = Integer.MAX_VALUE;
}

反编译class的文件

public static void main(java.lang.String[]);
  Code:
   0:   ldc     #2; //int 2147483647
   2:   istore_1
   3:   return
}

可以看到上面代码第三行是采用ldc指令将2147483647常量压入栈中,需要注意的是ldc指令是从常量池中获取值的,也就是说在这段范围(-2147483648~2147483647)内的int值是存储在常量池中的。

 

我们来看一个简单的事例:

public class Test{
    public static void main(String args[]) {
        int num= 50;
        num = num++*2;
        System.out.println(num);

    }
}

反编译class的文件

Compiled from "Test.java"
public class Test extends java.lang.Object{
public Test();
  Code:
   0:	aload_0
   1:	invokespecial	#1; //Method java/lang/Object."<init>":()V
   4:	return

public static void main(java.lang.String[]);
  Code:
   0:	bipush	50      # 该值被压入操作数堆栈,也就是将50压入栈中
   2:	istore_1        # 从栈中取出常量100存储到局部变量表中,下标索引为1
   3:	iload_1         # 将下标索引为1的常量从局部变量表中压入栈中
   4:	iinc	1, 1    # 将局部变量表中下标索引为1的变量自增   (栈中(50)的值没有发生变化,只是局部变量表(51)中的值发生了变化)
   7:	iconst_2        # iconst_2指令将常量压2入栈中
   8:	imul            # 从操作数堆栈中弹出值。两个值相乘*。该结果会被压入操作数栈。(这一步是栈中的值 50,2去除,然后相乘,所以结果为100)
   9:	istore_1        # 从栈中取出常量100存储到局部变量表中,下标索引为1
   10:	getstatic	#2; //Field java/lang/System.out:Ljava/io/PrintStream;
   13:	iload_1         # 将下标索引为1的常量从局部变量表中压入栈中
   14:	invokevirtual	#3; //Method java/io/PrintStream.println:(I)V
   17:	return           #返回值

}

所以最后执行结果是100


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值