javap实例分析

一.基本命令及原始代码

  本文分析使用的源代码如下:

1 public class StringTest{
2     public static void main(String[] args){
3         String a = "a" + "b" + 1;
4         String b = "ab1";
5         System.out.println(a == b);
6     }
7 }

  使用javap命令进行反编译:

1 javac -g:vars,lines StringTest.java
2 
3 javap -verbose StringTest

  反编译的结果如下:

 1 public class StringTest
 2   minor version: 0
 3   major version: 52
 4   flags: ACC_PUBLIC, ACC_SUPER
 5 Constant pool:
 6    #1 = Methodref          #6.#25         // java/lang/Object."<init>":()V
 7    #2 = String             #26            // ab1
 8    #3 = Fieldref           #27.#28        // java/lang/System.out:Ljava/io/PrintStream;
 9    #4 = Methodref          #29.#30        // java/io/PrintStream.println:(Z)V
10    #5 = Class              #31            // StringTest
11    #6 = Class              #32            // java/lang/Object
12    #7 = Utf8               <init>
13    #8 = Utf8               ()V
14    #9 = Utf8               Code
15   #10 = Utf8               LineNumberTable
16   #11 = Utf8               LocalVariableTable
17   #12 = Utf8               this
18   #13 = Utf8               LStringTest;
19   #14 = Utf8               main
20   #15 = Utf8               ([Ljava/lang/String;)V
21   #16 = Utf8               args
22   #17 = Utf8               [Ljava/lang/String;
23   #18 = Utf8               a
24   #19 = Utf8               Ljava/lang/String;
25   #20 = Utf8               b
26   #21 = Utf8               StackMapTable
27   #22 = Class              #17            // "[Ljava/lang/String;"
28   #23 = Class              #33            // java/lang/String
29   #24 = Class              #34            // java/io/PrintStream
30   #25 = NameAndType        #7:#8          // "<init>":()V
31   #26 = Utf8               ab1
32   #27 = Class              #35            // java/lang/System
33   #28 = NameAndType        #36:#37        // out:Ljava/io/PrintStream;
34   #29 = Class              #34            // java/io/PrintStream
35   #30 = NameAndType        #38:#39        // println:(Z)V
36   #31 = Utf8               StringTest
37   #32 = Utf8               java/lang/Object
38   #33 = Utf8               java/lang/String
39   #34 = Utf8               java/io/PrintStream
40   #35 = Utf8               java/lang/System
41   #36 = Utf8               out
42   #37 = Utf8               Ljava/io/PrintStream;
43   #38 = Utf8               println
44   #39 = Utf8               (Z)V
45 {
46   public StringTest();
47     descriptor: ()V
48     flags: ACC_PUBLIC
49     Code:
50       stack=1, locals=1, args_size=1
51          0: aload_0
52          1: invokespecial #1                  // Method java/lang/Object."<init>":()V
53          4: return
54       LineNumberTable:
55         line 1: 0
56       LocalVariableTable:
57         Start  Length  Slot  Name   Signature
58             0       5     0  this   LStringTest;
59 
60   public static void main(java.lang.String[]);
61     descriptor: ([Ljava/lang/String;)V
62     flags: ACC_PUBLIC, ACC_STATIC
63     Code:
64       stack=3, locals=3, args_size=1
65          0: ldc           #2                  // String ab1
66          2: astore_1
67          3: ldc           #2                  // String ab1
68          5: astore_2
69          6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
70          9: aload_1
71         10: aload_2
72         11: if_acmpne     18
73         14: iconst_1
74         15: goto          19
75         18: iconst_0
76         19: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
77         22: return
78       LineNumberTable:
79         line 3: 0
80         line 4: 3
81         line 5: 6
82         line 6: 22
83       LocalVariableTable:
84         Start  Length  Slot  Name   Signature
85             0      23     0  args   [Ljava/lang/String;
86             3      20     1     a   Ljava/lang/String;
87             6      17     2     b   Ljava/lang/String;
88       StackMapTable: number_of_entries = 2
89         frame_type = 255 /* full_frame */
90           offset_delta = 18
91           locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String ]
92           stack = [ class java/io/PrintStream ]
93         frame_type = 255 /* full_frame */
94           offset_delta = 0
95           locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String ]
96           stack = [ class java/io/PrintStream, int ]
97 }

 二. 字节码分析

1.常量池分析

  首先我们看比较靠前的一个部分是:“常量池”(Constant pool),每一项都以“const #数字”开头,这个数字是顺序递增的,通常把它叫做常量池的入口位置,当程序中需要使用到常量池的时候,就会在程序的对应位置记录下入口位置的标识符(在字节码文件中,就像一个列表一样,列表中的每一项存放的内容和长度是不一样的而已)。

  根据入口位置肯定是要找某些常量内容,常量内容会分为很多种。在每个常量池项最前面的1个字节,来标志常量池的类型(我们看到的Method、String等等都是经过映射转换后得到的,字节码中本身只会有1个字节来存放)。

  找到类型后,接下来就是内容,内容可以是直接存放在这个常量池的入口中,也可能由其它的一个或多个常量池域组合而成,听起来蛮抽象 ,下面通过例子来说明:

  第一部分:

 1 Constant pool:
 2    #1 = Methodref          #6.#25         // java/lang/Object."<init>":()V
 3    入口位置#1,简称入口#1,代表一个方法入口,方法入口由:入口#6 和 入口#25两者一起组成,中间用了一个“.”。
 4    #2 = String             #26            // ab1
 5    #3 = Fieldref           #27.#28        // java/lang/System.out:Ljava/io/PrintStream;
 6    #4 = Methodref          #29.#30        // java/io/PrintStream.println:(Z)V
 7    #5 = Class              #31            // StringTest
 8    #6 = Class              #32            // java/lang/Object
 9    //1>. 入口#6为一个Class对象(java/lang/Object),所以它引用了入口#32的常量池。
10    #7 = Utf8               <init>
11    #8 = Utf8               ()V
12    //2>. 入口#7是一个常量池内容,<init>;代表构造方法的意思。
13    //3>. 入口#8 也是一个真正的常量,值为()V,代表没有入口参数,返回值为void,
14    //4>. 将入口#7和入口#8反推到入口#32,就代表名称为构造方法的名称,入口参数个数为0,返回值为void的意思。
15          入口#32是一个常量,它的值是“java/lang/Object;”,但这只是一个字符串值,反推到入口#616    //5>. 入口#6要求这个字符串代表的是一个类,那么自然代表的类是java.lang.Object。
17    //6>. 综合起来就是:java.lang.Object类的构造方法,入口参数个数为0,返回值为void,
18          其实这在const #1后面的备注中已经标识出来了(这在字节码中本身不存在,只是javap工具帮助合并的)。

  第二部分:

1 #2 = String             #26            // ab1
2 //1>. 代表这是一个String类型的引用入口,本入口引用的内容为入口#26的值
3 #26 = Utf8               ab1
4 //1>. 代表当前常量池中存放的内容为ab1
5 //2>.综合起来就是:一个String对象的常量,存放的值为ab1

   第三部分:

 1    #3 = Fieldref           #27.#28        // java/lang/System.out:Ljava/io/PrintStream;
 2    
 3    #27 = Class              #35            // java/lang/System
 4         #35 = Utf8               java/lang/System
 5    #28 = NameAndType        #36:#37        // out:Ljava/io/PrintStream;
 6        #36 = Utf8               out
 7        #37 = Utf8               Ljava/io/PrintStream;
 8 
 9    
10    
11    #4 = Methodref          #29.#30        // java/io/PrintStream.println:(Z)V
12    
13    #29 = Class              #34            // java/io/PrintStream
14         #34 = Utf8               java/io/PrintStream
15    #30 = NameAndType        #38:#39        // println:(Z)V
16         #38 = Utf8               println
17         #39 = Utf8               (Z)V
18         
19    //1>. 入口#3是获取到java/lang/System类的属性out,out的类型是Ljava/io/PrintStream;
20  
21    //2>. 入口#4是调用java/io/PrintStream类的println方法,方法的返回值类型是void,入口类型是boolean。

 2.方法分析

  方法一(构造函数):

 1 public StringTest();
 2     descriptor: ()V
 3     flags: ACC_PUBLIC
 4     Code:
 5       stack=1, locals=1, args_size=1
 6          0: aload_0
 7          1: invokespecial #1                  // Method java/lang/Object."<init>":()V
 8          4: return
 9       LineNumberTable:
10         line 1: 0
11       LocalVariableTable:
12         Start  Length  Slot  Name   Signature
13             0       5     0  this   LStringTest;

 

  这是一个构造方法,程序中我们没有写构造方法,但是Java自己会帮我们生成一个,说明这个动作是在编译时完成的。虽然是构造方法,但是它足够简单,所以我们先从它开始来说 :

  stack=1, locals=1, args_size=1

  • 这一行是所有方法都会有的,其中stack代表栈的深度(单位大小为一个slot的大小),当一个数据被使用时会被先放到栈顶,使用完回写到本地变量或主存中,这里栈的深度为1,代表有一个this将会被使用。
  • locals是本地变量的slot个数,但是并不代表是stack宽度一致,本地变量是在这个方法生命周期内,局部变量最多的时候,需要多大的宽度来存放数据(double、long会占用两个slot)。

  • args_size代表的是入参的个数。
  1. 0:   aload_0

    首先第一个0代表虚指令中的行号(后面会应到,确切说应该是方法的body部分第几个字节),每个方法从0开始顺序递增,但是可以跳跃,跳跃的原因在于一些指令还会接操作的内容,这些操作的内容可能来自常量池,也可以标志是第几个slot的本地变量,因此需要占用一定的空间。

    aload_0指令是将“第1个”slot所在的本地变量推到栈顶,并且这个本地变量是引用类型的,相关的指令有:aload_[0-3](范围是:0x2a ~ 0x2d)。如果超过4个,则会使用“aload + 本地变量的slot位置”来完成(此时会多占用1个字节来存放),前者是通过具体的几个指令直接完成。

  2. 1: invokespecial #1 // Method java/lang/Object."<init>":()V

    指令中的第2个行号,执行invokespecial指令,这个指令是当发生构造方法调用、父类的构造方法调用、非静态的private方法调用会使用该指令,这里需要从常量池中获取一个方法,这个地方会占用2个字节的宽度,加上指令本身就是3个字节,因此下一个行号是4。

  3. 4: return
    最后一行是一个return,我们虽然没有自己写return,但是JVM中会自动在编译时加上。

  最后是局部变量表:

LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LStringTest;

  代表本地变量的列表,这里代表本地变量的作用域起始位置为0,作用域宽度为5(0-4),slot的起始位置也是0,名称为this,类型为StringTest。

方法二:

 1 public static void main(java.lang.String[]);
 2     descriptor: ([Ljava/lang/String;)V
 3     flags: ACC_PUBLIC, ACC_STATIC
 4     Code:
 5       stack=3, locals=3, args_size=1
 6          0: ldc           #2                  // String ab1
 7          /**
 8          1>.从常量入口#2取出内容推到栈顶,这里的string也是引用,但它是常量,所以用ldc,不是aload指令
 9          */
10          2: astore_1
11          /**
12          2>.将栈顶的引用值,写入第一个slot所在的本地变量中(a)
13          */
14          3: ldc           #2                  // String ab1
15          5: astore_2
16          6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
17          /**
18          3>.获取静态域放入栈顶,引用了常量池入口#3来获得,此时静态区域是System类中的out对象。
19          */
20          9: aload_1
21         10: aload_2
22         /**
23          4>.分别将a、b加载至栈顶
24          */
25         11: if_acmpne     18
26         14: iconst_1
27         15: goto          19
28         18: iconst_0
29         19: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
30         /**
31          3>.判断两个栈顶的引用是否一致(地址),对比处理的结束位置是第18行。
32          if_acmpne操作会将之前的两个操作数从栈顶pop出来,因此栈顶最多只有3个
33          如果一致则将常量值_1写入栈顶,也就是true
34          如果不一致则将常量值_0写入栈顶,也就是false
35          */
36         22: return
37       LineNumberTable:
38         line 3: 0
39         line 4: 3
40         line 5: 6
41         line 6: 22
42       LocalVariableTable:
43         Start  Length  Slot  Name   Signature
44             0      23     0  args   [Ljava/lang/String;
45             3      20     1     a   Ljava/lang/String;
46             6      17     2     b   Ljava/lang/String;

 

    

 

posted on 2018-09-23 11:21  水滴石穿水滴石穿  阅读(556)  评论(0编辑  收藏  举报

导航