前邊我們解析了一個字節碼文件,現在我們做一個比較複雜的字節碼文件的解析,程序如下:
package com.twodragonlake.jvm.bytecode;
public class MyTest2 {
String str = "Welcome";
private int x = 5;
public static Integer in = 10;
public static void main(String[] args){
MyTest2 myTest2 = new MyTest2();
myTest2.setX(8);
in = 20;
}
private void setX(int x){
this.x = x;
}
}
注意方法setX是私有的,我們這個時候使用javap -verbose 反編譯的字節碼不會展示私有方法,命令需要加上參數 -p:
javap -verbose -p com.twodragonlake.jvm.bytecode.MyTest2
私有的setX出來的字節碼:
private void setX(int);
descriptor: (I)V
flags: ACC_PRIVATE
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: iload_1
2: putfield #4 // Field x:I
5: return
LineNumberTable:
line 20: 0
line 21: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lcom/twodragonlake/jvm/bytecode/MyTest2;
0 6 1 x I
然後我們在setX方法上加上synchronized關鍵字,反編譯結果:
private synchronized void setX(int);
descriptor: (I)V
flags: ACC_PRIVATE, ACC_SYNCHRONIZED
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: iload_1
2: putfield #4 // Field x:I
5: return
LineNumberTable:
line 20: 0
line 21: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lcom/twodragonlake/jvm/bytecode/MyTest2;
0 6 1 x I
synchronized 關鍵字是爲了多線程併發加鎖,按照正常的套路應該會出現monitorenter和monitorexit但是反編譯出來的結果並沒有,只是在方法的聲明上邊加了ACC_SYNCHRONIZED的標記,這種synchronized的使用方式,加在方法上邊,默認是對當前對象加鎖,還有一種是對代碼塊加鎖,然後指定加鎖對象是指定的一個對象,比如:
private void test(String str){
synchronized (str){
System.out.println("hello world");
}
}
我們反編譯之後:
private void test(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PRIVATE
Code:
stack=2, locals=4, args_size=2
0: aload_1
1: dup
2: astore_2
3: monitorenter //加鎖
4: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #11 // String hello world
9: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: aload_2
13: monitorexit //解鎖
14: goto 22
17: astore_3
18: aload_2
19: monitorexit //這一行的目的是爲了在 程序拋出異常(21: athrow)之前保證正常的釋放鎖,如果異常不釋放鎖,
//那麼就會陷入混亂
20: aload_3
21: athrow //拋出異常
22: return
Exception table:
from to target type
4 14 17 any
17 20 17 any
LineNumberTable:
line 24: 0
line 25: 4
line 26: 12
line 27: 22
LocalVariableTable:
Start Length Slot Name Signature
0 23 0 this Lcom/twodragonlake/jvm/bytecode/MyTest2;
0 23 1 str Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 17
locals = [ class com/twodragonlake/jvm/bytecode/MyTest2, class java/lang/String, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
這個時候我們想要的monitorenter 和 monitorexit出現了。
monitorenter 入口只有一個,但是monitorexit的出口有多個,因爲程序異常也會執行monitorexit
synchronized 除了修飾實例方法和代碼塊之外還可以修飾靜態方法,修飾靜態方法的時候是給Class加鎖:
private synchronized static void tes2(){
}
反編譯如下:
private static synchronized void tes2();
descriptor: ()V
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=0, locals=0, args_size=0
0: return
LineNumberTable:
line 31: 0
這個時候 只會在方法的標記一下 ACC_SYNCHRONIZED,因爲synchronized 屬於class上鎖。
俺麼接下來我們就如下的完整的字節碼文件進行解析:
package com.twodragonlake.jvm.bytecode;
public class MyTest2 {
String str = "Welcome";
private int x = 5;
public static Integer in = 10;
public static void main(String[] args){
MyTest2 myTest2 = new MyTest2();
myTest2.setX(8);
in = 20;
}
private synchronized void setX(int x){
this.x = x;
}
private void test(String str){
synchronized (str){
System.out.println("hello world");
}
}
private synchronized static void tes2(){
}
}
拿到反編譯的1進制文件:
首先是魔數:CAFEBABE
版本號+小版本號:00000034
常量池2+n個字節 : 0046 常量個數:70 (實際69個,其中一個是保留的)
第一個常量:0A 是10 代表方法引用,00 0D 是第13號常量,00 2D是第45 號常量,最終彙總出來:#13.#45 // java/lang/Object.””:()V
第二個常量:08 是字符串索引,00 2E是46號常量 字符串:welcome。
第三個常量: 09 方法引用,00 05 是5號常量【#5 = Class #49 // com/twodragonlake/jvm/bytecode/MyTest2】;
00 2f 是47號常量:#47 = NameAndType #14:#15 // str:Ljava/lang/String;所以3號常量是:
// com/twodragonlake/jvm/bytecode/MyTest2.str:Ljava/lang/String;
第四個常量:09 方法引用,00 05 是5號常量【#5 = Class #49 // com/twodragonlake/jvm/bytecode/MyTest2】; 00 30 是48號常量
【#48 = NameAndType #16:#17 // x:I】 ,所以四號常量是:【#5.#48 // com/twodragonlake/jvm/bytecode/MyTest2.x:I】
第五個常量: 07 是類的引用,00 31 是49號常量 【#49 = Utf8 com/twodragonlake/jvm/bytecode/MyTest2】;
第六個常量:0A 是方法引用,00 05 是5號常量【#5 = Class #49 // com/twodragonlake/jvm/bytecode/MyTest2】;
00 2D 是45號常量【#45 = NameAndType #20:#21 // <init>
:()V】 所以6號常量是:
【#6 = Methodref #5.#45 // com/twodragonlake/jvm/bytecode/MyTest2.<init>
:()V】
第七個常量:0A 是 方法引用 ,00 05 , 00 32 分別是5號和50號常量: 【#7 = Methodref #5.#50 // com/twodragonlake/jvm/bytecode/MyTest2.setX:(I)V】
第八個常量:0A 是方法引用,00 33 00 34 分別是51和52號常量:【#8 = Methodref #51.#52 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;】
第九個常量:09 是字段引用,00 05 00 35 分別是5號和53號常量:【#9 = Fieldref #5.#53 // com/twodragonlake/jvm/bytecode/MyTest2.in:Ljava/lang/Integer;】
第十個常量:09 是字段引用,00 36 00 37 分別是54和55號常量:【#10 = Fieldref #54.#55 // java/lang/System.out:Ljava/io/PrintStream;】
第十一個常量:08字符串引用,00 38 是56號常量:【 #56 = Utf8 hello world】
是十二號常量:0A是方法引用,00 39 和 00 3A分別是57號和58號常量:【#12 = Methodref #57.#58 // java/io/PrintStream.println:(Ljava/lang/String;)V】
第十三號常量:07 是類引用,00 3B是59號常量:【#13 = Class #59 // java/lang/Object】
第十四號常量:01 字符串信息,00 03 是三個utf-8字節碼長度,73 74 72 是對應的utf編碼的 str。
第十五號常量:01是字符串引用,00 12 是18個utf-8編碼的長度, 4C6A6176612F6C616E672F537472696E673B 是 【Ljava/lang/String;】字符串
第十六號常量:01 是字符串 引用,00 01 是一個長度,78 是字符串x。
第十七號常量:01 是字符串,00 01一個長度,49是I。
第十八號常量:01 是字符串,00 02是2個長度,69 6E in
第十九號常量:01是字符串,00 13 是19個長度,4C6A6176612F6C616E672F496E74656765723B 是Ljava/lang/Integer;
第二十號常量:01 是字符串,00 06是6個長度,3C696E69743E 是 <init>
第二十一號常量:01 是字符串, 00 03 是三個長度,28 29 56是()V
第二十二號常量:01是字符串, 00 04 是4個長度,436F6465 是 Code
第二十三號常量:01 是字符串,00 0F是15個長度,4C696E654E756D6265725461626C65 是LineNumberTable
第二十四號常量:01是字符串,00 12 是18個長度,4C6F63616C5661726961626C655461626C65 是LocalVariableTable
第二十五號常量:01 是字符串,00 04 是四個長度, 74 68 69 73 是字符串:this
第二十六號常量:01 是字符串 00 28 是40個長度,4C636F6D2F74776F647261676F6E6C616B652F6A766D2F62797465636F64652F4D7954657374323B 是字符串:Lcom/twodragonlake/jvm/bytecode/MyTest2;
第二十七號常量:01是字符串,00 04 是四個長度,6D 61 69 6E是字符串:main
第二十八號常量:01是字符串 ,0016 是38個長度,285B4C6A6176612F6C616E672F537472696E673B2956 是字符串:([Ljava/lang/String;)V
第二十九號常量:01 是字符串,00 04 是四個長度,61 72 67 73 字符串args
第三十號常量:01是字符串,00 13 是18個長度,5B4C6A6176612F6C616E672F537472696E673B 是字符串[Ljava/lang/String;
第三十一號常量:01是字符串,00 07是7個長度,6D795465737432 是字符串:myTest2
第三十二號常量:01 是字符串,00 04 是4個長度,73 65 74 58是字符串:setX
第三十三號常量:01是字符串 00 04是4個長度,28 49 29 56 是字符串:(I)V
第三十四 號常量:01是字符串,00 04 是四個長度,74 65 73 74 是字符串:test
第三十五個常量:01 是字符串,00 15 是21個長度,284C6A6176612F6C616E672F537472696E673B2956 是字符串:(Ljava/lang/String;)V
第三十六常量:01是字符串, 00 0D,是13個長度,537461636B4D61705461626C65 是字符串:StackMapTable
第三十七個常量:07 是類引用信息,00 31 是49號常量【#49 = Utf8 com/twodragonlake/jvm/bytecode/MyTest2】;
第三十八號常量:07是類信息,00 3C 60號常量 【#38 = Class #60 // java/lang/String】
第三十九號常量:07是類信息,00 3B是59號常量:【 #39 = Class #59 // java/lang/Object】
第四十號常量:07是類信息,00 3D是61號常量【#40 = Class #61 // java/lang/Throwable】
第四十一號常量:01是字符串,00 04 是四個長度,74 65 73 32 是字符串test2
第四十二號常量:01 是字符串,00 08 是8個長度,3C636C696E69743E 是字符串 【<clinit>
】
第四十三號常量:01是字符串,00 0A,是10個長度,536F7572636546696C65 是字符串:【SourceFile】
第四十四號常量:01是字符串。00 0C 是12個長度,4D7954657374322E6A617661 是字符串:【MyTest2.java】
第四十五號常量:0C 名稱和類型的引用,00 14 是20號常量,00 15 是21號常量:【#20:#21 //"<init>"
:()V】
第四十六號常量:01是字符串,00 07 是7個長度,57656C636F6D65 是字符串:【Welcome】
第四十七號常量:0C 名稱和類型的引用,00 0E 是14號常量,00 0F是15號常量,所以彙總是:【#47 = NameAndType #14:#15 // str:Ljava/lang/String;】
第四十八號常量:0C是名稱和類型的引用,00 10 00 11 分別是16和17號常量:【#48 = NameAndType #16:#17 // x:I】
第四十九號常量:01 是字符串,00 26是38個長度,636F6D2F74776F647261676F6E6C616B652F6A766D2F62797465636F64652F4D795465737432 是字符串:
【com/twodragonlake/jvm/bytecode/MyTest2】
第五十號常量:0C是名稱和類型的引用,00 20 是32號常量,0021是33號常量,彙總:【#50 = NameAndType #32:#33 // setX:(I)V】
第五十一號常量:07 類引用信息,00 3E是62號常量:彙總:【#51 = Class #62 // java/lang/Integer】
第五十二號常量:0C是名稱和類型的引用, 00 3F是63號常量,00 40是64號常量,彙總:【#52 = NameAndType #63:#64 // valueOf:(I)Ljava/lang/Integer;】
第五十三號常量:0C 是名稱和類型的引用,00 12是18號常量,00 13是19號常量,彙總【#53 = NameAndType #18:#19 // in:Ljava/lang/Integer;】
第五十四號常量:07是類引用信息,0041是6號常量:【#54 = Class #65 // java/lang/System】
第五十五號常量:0C 是 名稱和類型的引用, 0042 是66號常量,0042 是67號常量彙總;【#55 = NameAndType #66:#67 // out:Ljava/io/PrintStream;】
第五十六號常量:01是字符串,00 0B 是11個長度,68656C6C6F20776F726C64是字符串:【hello world】
第五十七號常量:07 是類引用信息,00 44 是68號常量:【#57 = Class #68 // java/io/PrintStream】
第五十八號常量:0C 是 名稱和類型的引用, 00 45 是69號常量,00 23是35號常量,彙總:【#58 = NameAndType #69:#35 // println:(Ljava/lang/String;)V】
第五十九號常量:01 是字符串,00 10 是16個長度,6A6176612F6C616E672F4F626A656374 字符串是:【java/lang/Object】
第六十號常量:01 是字符串,00 10 是1個長度,6A6176612F6C616E672F537472696E67 是字符串 【java/lang/String】
第六十一號常量:01 是字符串,00 13是35個長度,6A6176612F6C616E672F5468726F7761626C65 是字符串:java/lang/Throwable
第六十二號常量:01是字符串,00 11 是17個長度,6A6176612F6C616E672F496E7465676572 是字符串:java/lang/Integer
第六十三號常量:01 是字符串, 00 07是7個長度,76616C75654F66 是字符串:valueOf
第六十四號常量:01時候字符串,00 16是22個長度,2849294C6A6176612F6C616E672F496E74656765723B 是字符串:(I)Ljava/lang/Integer;
第六十五號常量:01 是字符串,00 10是16個長度,6A6176612F6C616E672F53797374656D是字符串:java/lang/System
第六十六號常量:01 是字符串,00 03 是三個長度,6F 75 74 是字符串:out
第六十七號常量:01 是字符串, 00 15是21個長度,4C6A6176612F696F2F5072696E7453747265616D3B 是字符串:Ljava/io/PrintStream;
第六十八號常量:01是字符串,00 13是19個長度,6A6176612F696F2F5072696E7453747265616D 是字符竄:java/io/PrintStream
第六十九號常量:01是字符串,00 07 是7個長度,7072696E746C6E 是字符串:println
爲此常量池到此結束: