jvm原理(28)synchronized關鍵字所生成的字節碼詳細分析&&複雜字節碼文件的分析過程

前邊我們解析了一個字節碼文件,現在我們做一個比較複雜的字節碼文件的解析,程序如下:

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
爲此常量池到此結束:
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章