通過字節碼扒一扒java編譯器瞞着我們做了什麼(1)

 1.       Foreach和泛型語法糖

Map<String,String> map = newHashMap<String,String>();

for(Entry<String, String> iter:test.map.entrySet()){

                     Stringstr = iter.getValue();

           }

--看看字節碼是什麼內容:

      8:aload_1

       9: getfield      #27                 // Field map:Ljava/util/Map;

       12: invokeinterface #44,  1           // InterfaceMethod java/util/Map.e

ntrySet:()Ljava/util/Set;

       17: invokeinterface #48,  1           // InterfaceMethodjava/util/Set.i

terator:()Ljava/util/Iterator;

       22: astore_3

       23: goto          47

       26: aload_3

       27: invokeinterface #54,  1           // InterfaceMethodjava/util/Itera

tor.next:()Ljava/lang/Object;

       32: checkcast     #60                 // class java/util/Map$Entry

       35: astore_2

       36: aload_2

       37: invokeinterface #62,  1           // InterfaceMethod java/util/Map$E

ntry.getValue:()Ljava/lang/Object;

       42: checkcast     #65                 // class java/lang/String

       45: astore        4

       47: aload_3

       48: invokeinterface #67,  1           //InterfaceMethod java/util/Itera

tor.hasNext:()Z

        53: ifne          26

--可以看到紅色那些內容,說明實際上編譯器真正使用的還是正常的for循環加迭代器遍歷的套路,只不過爲了讓使用者感覺方便創造了foreach這種看似簡單易用的語法糖。還有綠色的checkcast,說明Map<String,String> 這個泛型其實也是顆語法糖,跟C++不一樣,JVM並不會創建一個Map<String,String>類型,只是編譯器在每個使用到Map<String,String>的代碼中,進行了類型轉化,可以根據InterfaceMethodjava/util/Map$Entry.getValue:()Ljava/lang/Object;可知道iter.getValue()實際返回值只是Object對象,編譯器添加了向下轉化代碼checkcast,所以整個代碼經過編譯器轉化後應該是這樣的:String str = (String)iter.getValue();

2.  接口文件的字節碼內容

接口文件字節碼的內容是什麼呢?這是個有意思的問題,首先看到接口文件其實也是生成一份.class文件,再看看字節碼經過javap轉譯後是什麼內容:

Classfile/D:/javadevelop/eclipse/user/workspace/RedisDao/bin/RedisDao.class

  Lastmodified 2017-7-19; size 3918 bytes

  MD5checksum 1389d391e77bd4dba7e05d39f38182b9

 Compiled from "RedisDao.java"

public interface RedisDao

  minorversion: 0

 major version: 52

 flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT

Constant pool:

。。。。。。省略

{

 public abstract java.lang.StringbatchSetString(redis.clients.jedis.Jedis, jav

a.lang.String[], boolean);

   descriptor:(Lredis/clients/jedis/Jedis;[Ljava/lang/String;Z)Ljava/lang/Stri

ng;

   flags: ACC_PUBLIC, ACC_ABSTRACT

 

 public abstract java.util.List<java.lang.String>batchGetString(redis.clients.

jedis.Jedis, java.lang.String[]);

   descriptor: (Lredis/clients/jedis/Jedis;[Ljava/lang/String;)Ljava/util/List;

 

   flags: ACC_PUBLIC, ACC_ABSTRACT

   Signature: #10                         // (Lredis/clients/jedis/Jedis;[Ljav

a/lang/String;)Ljava/util/List<Ljava/lang/String;>;

}

--是的,其實跟正常類的字節碼佈局基本一樣,只是方法都被編譯器加上了abstract關鍵字(在源碼中並沒寫該關鍵字),而且沒有CODE段。所以說所謂的“接口”其實本質上就是被編譯器閹割(限制)的類而且,就像一個普通人皈依了某宗教,被教律限制了不能做這不能做那,但是對於JVM而言,跟正常類區別不大。另外有特點,如果在定義接口時沒有寫訪問控制權限和抽象關鍵字,編譯器會在生成字節碼時在接口前面添加public abstract,所以出現了一個這麼特點:你可以不寫public abstract,編譯器會爲你加上,但是你不能寫其他限定詞,否則編譯器就報錯不通過。

 

2.       抽象類的字節碼內容:

Classfile /D:/javadevelop/eclipse/user/workspace/RedisDao/bin/ChildTest.class

  Last modified 2017-7-20; size 671 bytes

  MD5 checksum 64cf89af9ee1f820eac71c6f59759a78

  Compiled from "ChildTest.java"

public abstractclass ChildTest extends test

  minor version: 0

  majorversion: 52

  flags: ACC_PUBLIC, ACC_SUPER, ACC_ABSTRACT

Constant pool:

。。。。省略

protected abstractvoid fun();

   descriptor: ()V

   flags: ACC_PROTECTED, ACC_ABSTRACT

。。。。省略

--可見抽象類和正常類的佈局 也基本相同,並且抽象類也是獨立生成一個.class文件的,所以說“接口”和“抽象類”其實更多是java上的概念,而這個概念其實是對正常類的閹割,這不能做,那不能做,而這個閹割由編譯器來實現(如果違反閹割規則編譯器不允許通過編譯)。但是從字節碼角度看,其實相差不大,都是正常的.class佈局。

 

3.       從字節碼角度看try、catch、finally機制

public void fun(){

            try{

                     Filefile = new File("");

            }catch(Exceptione){

                     e.printStackTrace();

                     return;

            }finally{

                     ((Throwable) e).getMessage();

            }

}

--這段代碼編譯成字節碼:

 public void fun();

   descriptor: ()V

   flags: ACC_PUBLIC

   Code:

     stack=3, locals=3, args_size=1

         0: new           #34                 // class java/io/File

         3: dup

         4: ldc           #36                 // String

         6: invokespecial #38                 // Methodjava/io/File."<init>":(L

java/lang/String;)V

         9: astore_1

        10: goto          44

        13: astore_1

        14: aload_1

        15: invokevirtual #41                 // Methodjava/lang/Exception.prin

tStackTrace:()V

        18: aload_0

        19: getfield      #46                 // Field e:Ljava/lang/Object;

        22: checkcast     #50                 // class java/lang/Throwable

        25: invokevirtual #52                 // Methodjava/lang/Throwable.getM

essage:()Ljava/lang/String;

        28: pop

        29: return

        30: astore_2

        31: aload_0

        32: getfield      #46                 // Field e:Ljava/lang/Object;

        35: checkcast     #50                 // class java/lang/Throwable

        38: invokevirtual #52                 // Methodjava/lang/Throwable.getM

essage:()Ljava/lang/String;

        41: pop

        42: aload_2

        43: athrow

        44: aload_0

        45: getfield      #46                 // Field e:Ljava/lang/Object;

        48: checkcast     #50                 // class java/lang/Throwable

        51: invokevirtual #52                 // Methodjava/lang/Throwable.getM

essage:()Ljava/lang/String;

        54: pop

        55: return

      Exception table:

         from   to  target type

             0    10   13   Class java/lang/Exception

             0    18   30   any

     LineNumberTable:

       line 28: 0

       line 29: 10

       line 30: 14

       line 33: 18

       line 31: 29

       line 32: 30

       line 33: 31

       line 34: 42

       line 33: 44

       line 35: 55

     LocalVariableTable:

       Start  Length  Slot Name   Signature

           0      56    0  this   LChildTest;

          14      16     1    e   Ljava/lang/Exception;

     StackMapTable: number_of_entries = 3

       frame_type = 77 /* same_locals_1_stack_item */

         stack = [ class java/lang/Exception ]

       frame_type = 80 /* same_locals_1_stack_item */

         stack = [ class java/lang/Throwable ]

        frame_type = 13 /* same */

如果運行時不拋出異常則按綠色標識的CODE段的正常流程走,看看正常流程是怎麼走的:1.執行代碼File file = new File("");,2. Goto 第44字節開始的命令,3.第44字節開始的命令就是((Throwable) e).getMessage();

如果運行時拋出異常呢?注意紅色那段,這是Exception table,當運行時try模塊拋出異常後,第一步就是去查詢Exception table,紅色那兩行是什麼意思呢?第一行意思就是當從0到10字節中拋出類型爲Exception或它的子類異常時,則指令應該跳轉到第13字節指令,第13字節開始的指令就是catch模塊的e.printStackTrace();,順着執行,然後執行finally模塊中的代碼。最後才執行catch模塊的return。第二行意思就是當從0到18字節指令也就是try和catch兩個模塊中拋出任何其他類型的異常時,第一步跳轉到第30字節指令,也就是finally模塊的代碼。

這樣一來就保證了無論如何finally模塊的代碼都必須執行,並且在return之前執行。

如果把finally模塊修改下:

finally{

                     thrownew Exception("zrf");

           }

則字節碼如下:

 public void fun() throws java.lang.Exception;

   descriptor: ()V

   flags: ACC_PUBLIC

Exceptions:

     throws java.lang.Exception

       22: new           #35                 // class java/lang/Exception

       25: dup

       26: ldc           #47                 // String zrf

       28: invokespecial #49                // Method java/lang/Exception."<in

it>":(Ljava/lang/String;)V

        31: athrow

--可見在fun方法的字節碼中增加了一個Exceptions: throws java.lang.Exception段,而且在finally模塊指令中執行athrow指令

 

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