java实例分析_javap实例分析

一.基本命令及原始代码

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

1 public classStringTest{2 public static voidmain(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.java2

3 javap -verbose StringTest

反编译的结果如下:

1 public classStringTest2 minor version: 0

3 major version: 52

4 flags: ACC_PUBLIC, ACC_SUPER5 Constant pool:6 #1 = Methodref #6.#25 //java/lang/Object."":()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

13 #8 =Utf8 ()V14 #9 =Utf8 Code15 #10 =Utf8 LineNumberTable16 #11 =Utf8 LocalVariableTable17 #12 = Utf8 this

18 #13 =Utf8 LStringTest;19 #14 =Utf8 main20 #15 = Utf8 ([Ljava/lang/String;)V21 #16 =Utf8 args22 #17 = Utf8 [Ljava/lang/String;23 #18 =Utf8 a24 #19 = Utf8 Ljava/lang/String;25 #20 =Utf8 b26 #21 =Utf8 StackMapTable27 #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 //"":()V

31 #26 =Utf8 ab132 #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 StringTest37 #32 = Utf8 java/lang/Object38 #33 = Utf8 java/lang/String39 #34 = Utf8 java/io/PrintStream40 #35 = Utf8 java/lang/System41 #36 =Utf8 out42 #37 = Utf8 Ljava/io/PrintStream;43 #38 =Utf8 println44 #39 =Utf8 (Z)V45 {46 publicStringTest();47 descriptor: ()V48 flags: ACC_PUBLIC49 Code:50 stack=1, locals=1, args_size=1

51 0: aload_052 1: invokespecial #1 //Method java/lang/Object."":()V

53 4: return

54 LineNumberTable:55 line 1: 0

56 LocalVariableTable:57 Start Length Slot Name Signature58 0 5 0 thisLStringTest;59

60 public static voidmain(java.lang.String[]);61 descriptor: ([Ljava/lang/String;)V62 flags: ACC_PUBLIC, ACC_STATIC63 Code:64 stack=3, locals=3, args_size=1

65 0: ldc #2 //String ab1

66 2: astore_167 3: ldc #2 //String ab1

68 5: astore_269 6: getstatic #3 //Field java/lang/System.out:Ljava/io/PrintStream;

70 9: aload_171 10: aload_272 11: if_acmpne 18

73 14: iconst_174 15: goto 19

75 18: iconst_076 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 Signature85 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."":()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/Object9 //1>. 入口#6为一个Class对象(java/lang/Object),所以它引用了入口#32的常量池。

10 #7 = Utf8

11 #8 =Utf8 ()V12 //2>. 入口#7是一个常量池内容,;代表构造方法的意思。13 //3>. 入口#8 也是一个真正的常量,值为()V,代表没有入口参数,返回值为void,14 //4>. 将入口#7和入口#8反推到入口#32,就代表名称为构造方法的名称,入口参数个数为0,返回值为void的意思。

15 入口#32是一个常量,它的值是“java/lang/Object;”,但这只是一个字符串值,反推到入口#6,16 //5>. 入口#6要求这个字符串代表的是一个类,那么自然代表的类是java.lang.Object。17 //6>. 综合起来就是:java.lang.Object类的构造方法,入口参数个数为0,返回值为void,

18 其实这在const #1后面的备注中已经标识出来了(这在字节码中本身不存在,只是javap工具帮助合并的)。

第二部分:

1 #2 = String #26 //ab12 //1>. 代表这是一个String类型的引用入口,本入口引用的内容为入口#26的值

3 #26 =Utf8 ab14 //1>. 代表当前常量池中存放的内容为ab15 //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/System5 #28 = NameAndType #36:#37 //out:Ljava/io/PrintStream;

6 #36 =Utf8 out7 #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/PrintStream15 #30 = NameAndType #38:#39 //println:(Z)V

16 #38 =Utf8 println17 #39 =Utf8 (Z)V18

19 //1>. 入口#3是获取到java/lang/System类的属性out,out的类型是Ljava/io/PrintStream;20

21 //2>. 入口#4是调用java/io/PrintStream类的println方法,方法的返回值类型是void,入口类型是boolean。

2.方法分析

方法一(构造函数):

1 publicStringTest();2 descriptor: ()V3 flags: ACC_PUBLIC4 Code:5 stack=1, locals=1, args_size=1

6 0: aload_07 1: invokespecial #1 //Method java/lang/Object."":()V

8 4: return

9 LineNumberTable:10 line 1: 0

11 LocalVariableTable:12 Start Length Slot Name Signature13 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代表的是入参的个数。

0:   aload_0

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

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

1: invokespecial #1 // Method java/lang/Object."":()V

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

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 voidmain(java.lang.String[]);2 descriptor: ([Ljava/lang/String;)V3 flags: ACC_PUBLIC, ACC_STATIC4 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_111 /**

12 2>.将栈顶的引用值,写入第一个slot所在的本地变量中(a)13 */

14 3: ldc #2 //String ab1

15 5: astore_216 6: getstatic #3 //Field java/lang/System.out:Ljava/io/PrintStream;

17 /**

18 3>.获取静态域放入栈顶,引用了常量池入口#3来获得,此时静态区域是System类中的out对象。19 */

20 9: aload_121 10: aload_222 /**

23 4>.分别将a、b加载至栈顶24 */

25 11: if_acmpne 18

26 14: iconst_127 15: goto 19

28 18: iconst_029 19: invokevirtual #4 //Method java/io/PrintStream.println:(Z)V

30 /**

31 3>.判断两个栈顶的引用是否一致(地址),对比处理的结束位置是第18行。32 if_acmpne操作会将之前的两个操作数从栈顶pop出来,因此栈顶最多只有3个33 如果一致则将常量值_1写入栈顶,也就是true34 如果不一致则将常量值_0写入栈顶,也就是false35 */

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 Signature44 0 23 0 args [Ljava/lang/String;45 3 20 1 a Ljava/lang/String;46 6 17 2 b Ljava/lang/String;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值