更多課程 選擇中心
        Java培訓

        400-111-8989

        Java培訓 > Java教程  > 正文

        圖文詳解 Java 字節碼,想不懂都難!

        • 發布:Java培訓
        • 來源:Java教程
        • 時間:2018-04-23 14:48

        即便對那些有經驗的Java開發人員來說,閱讀已編譯的Java字節碼也很乏味。為什么我們首先需要了解這種底層的東西?這是上周發生在我身上的一個簡單故事:很久以前,我在機器上做了一些代碼更改,編譯了一個JAR,并將其部署到服務器上,以測試性能問題的一個潛在修復方案。不幸的是,代碼從未被檢入到版本控制系統中,并且出于某種原因,本地更改被刪除了而沒有追蹤。幾個月后,我再次修改源代碼,但是我找不到上一次更改的版本!

        幸運的是編譯后的代碼仍然存在于該遠程服務器上。我于是松了一口氣,我再次抓取JAR并使用反編譯器編輯器打開它......只有一個問題:反編譯器GUI不是一個完美的工具,并且出于某種原因,在該JAR中的許多類中找到我想要反編譯的特定類并在我打開它時會在UI中導致了一個錯誤,并且反編譯器崩潰!

        絕望的時候需要采取孤注一擲的措施。幸運的是,我對原始字節碼很熟悉,我寧愿花些時間手動地對一些代碼進行反編譯,而不是通過不斷的更改和測試它們。因為我仍然記得在哪里可以查看代碼,所以閱讀字節碼幫助我精確地確定了具體的變化,并以源代碼形式構建它們。(我一定要從我的錯誤中吸取教訓,這次要珍惜好這些教訓!)

        字節碼的好處是,您可以只用學習它的語法一次,然后它適用于所有Java支持的平臺——因為它是代碼的中間表示,而不是底層CPU的實際可執行代碼。此外,字節碼比本機代碼更簡單,因為JVM架構相當簡單,因此簡化了指令集,另一件好事是,這個集合中的所有指令都是由Oracle提供完整的文檔。

        不過,在學習字節碼指令集之前,讓我們熟悉一下JVM的一些事情,這是進行下一步的先決條件。

        JVM 數據類型

        Java是靜態類型的,它會影響字節碼指令的設計,這樣指令就會期望自己對特定類型的值進行操作。例如,就會有好幾個add指令用于兩個數字相加:iadd、ladd、fadd、dadd。他們期望類型的操作數分別是int、long、float和double。大多數字節碼都有這樣的特性,它具有不同形式的相同功能,這取決于操作數類型。

        JVM定義的數據類型包括:

        基本類型:

        數值類型: byte (8位), short (16位), int (32位), long (64-bit位), char (16位無符號Unicode), float(32-bit IEEE 754 單精度浮點型), double (64-bit IEEE 754 雙精度浮點型)

        布爾類型

        指針類型: 指令指針。

        引用類型:

        數組

        接口

        在字節碼中布爾類型的支持是受限的。舉例來說,沒有結構能直接操作布爾值。布爾值被替換轉換成 int 是通過編譯器來進行的,并且最終還是被轉換成 int 結構。

        Java 開發者應該熟悉所有上面的類型,除了 returnAddress,它沒有等價的編程語言類型。

        基于棧的架構

        字節碼指令集的簡單性很大程度上是由于 Sun 設計了基于堆棧的 VM 架構,而不是基于寄存器架構。有各種各樣的進程使用基于JVM 的內存組件, 但基本上只有 JVM 堆需要詳細檢查字節碼指令:

        PC寄存器:對于Java程序中每個正在運行的線程,都有一個PC寄存器保存著當前執行的指令地址。

        JVM 棧:對于每個線程,都會分配一個棧,其中存放本地變量、方法參數和返回值。下面是一個顯示3個線程的堆棧示例。

        圖文詳解 Java 字節碼,想不懂都難!

        :所有線程共享的內存和存儲對象(類實例和數組)。對象回收是由垃圾收集器管理的。

        圖文詳解 Java 字節碼,想不懂都難!

        方法區:對于每個已加載的類,它儲存方法的代碼和一個符號表(例如對字段或方法的引用)和常量池。

        圖文詳解 Java 字節碼,想不懂都難!

        JVM堆棧是由幀組成的,當方法被調用時,每個幀都被推到堆棧上,當方法完成時從堆棧中彈出(通過正常返回或拋出異常)。每一幀還包括:

        本地變量數組,索引從0到它的長度-1。長度是由編譯器計算的。一個局部變量可以保存任何類型的值,long和double類型的值占用兩個局部變量。

        用來存儲中間值的棧,它存儲指令的操作數,或者方法調用的參數。

        圖文詳解 Java 字節碼,想不懂都難!

        字節碼探索

        關于JVM內部的看法,我們能夠從示例代碼中看到一些被生成的基本字節碼例子。Java類文件中的每個方法都有代碼段,這些代碼段包含了一系列的指令,格式如下:

        opcode (1 byte) operand1 (optional) operand2 (optional) ...

        這個指令是由一個一字節的opcode和零個或若干個operand組成的,這個operand包含了要被操作的數據。

        在當前執行方法的棧幀里,一條指令可以將值在操作棧中入棧或出棧,可以在本地變量數組中悄悄地加載或者存儲值。讓我們來看一個例子:

        圖文詳解 Java 字節碼,想不懂都難!

        為了打印被編譯的類中的結果字節碼(假設在Test.class文件中),我們運行javap工具:

        圖文詳解 Java 字節碼,想不懂都難!

        我們可以得到如下結果:

        圖文詳解 Java 字節碼,想不懂都難!

        我們可以看到main方法的方法聲明,descriptor說明這個方法的參數是一個字符串數組([Ljava/lang/String; ),而且返回類型是void(V)。下面的flags這行說明該方法是公開的(ACC_PUBLIC)和靜態的 (ACC_STATIC)。

        Code屬性是最重要的部分,它包含了這個方法的一系列指令和信息,這些信息包含了操作棧的最大深度(本例中是2)和在這個方法的這一幀中被分配的本地變量的數量(本例中是4)。所有的本地變量在上面的指令中都提到了,除了第一個變量(索引為0),這個變量保存的是args參數。其他三個本地變量就相當于源碼中的a,b和c。

        從地址0到8的指令將執行以下操作:

        iconst_1:將整形常量1放入操作數棧。

        圖文詳解 Java 字節碼,想不懂都難!

        istore_1:在索引為1的位置將第一個操作數出棧(一個int值)并且將其存進本地變量,相當于變量a。

        圖文詳解 Java 字節碼,想不懂都難!

        iconst_2:將整形常量2放入操作數棧。

        圖文詳解 Java 字節碼,想不懂都難!

        istore_2:在索引為2的位置將第一個操作數出棧并且將其存進本地變量,相當于變量b。

        圖文詳解 Java 字節碼,想不懂都難!

        iload_1:從索引1的本地變量中加載一個int值,放入操作數棧。

        圖文詳解 Java 字節碼,想不懂都難!

        iload_2:從索引2的本地變量中加載一個int值,放入操作數棧。

        圖文詳解 Java 字節碼,想不懂都難!

        iadd:把操作數棧中的前兩個int值出棧并相加,將相加的結果放入操作數棧。

        圖文詳解 Java 字節碼,想不懂都難!

        istore_3:在索引為3的位置將第一個操作數出棧并且將其存進本地變量,相當于變量c。

        圖文詳解 Java 字節碼,想不懂都難!

        return:從這個void方法中返回。

        上述指令只包含操作碼,由JVM來精確執行。

        方法調用

        上面的示例只有一個方法,即 main 方法。假如我們需要對變量 c 進行更復雜的計算,這些復雜的計算寫在新方法 calc 中:

        圖文詳解 Java 字節碼,想不懂都難!

        看看生成的字節碼:

        圖文詳解 Java 字節碼,想不懂都難!

        main 方法代碼唯一的不同在于用 invokestatic 指令代替了 iadd 指令,invokestatic 指令用于調用靜態方法 calc。注意,關鍵在于操作數棧中傳遞給 calc 方法的兩個參數。也就是說,調用方法需要按正確的順序為被調用方法準備好所有參數,交依次推入操作數棧。iinvokestatic(還有后面提到的其它類似的調用指令)隨后會從棧中取出這些參數,然后為被調用方法創建一個新的環境,將參數作為局域變量置于其中。

        我們也注意到invokestatic指令在地址上看占據了3字節,由6跳轉到9。不像其余指令那樣那么遠,這是因為invokestatic指令包含了兩個額外的字節來構造要調用的方法的引用(除了opcode外)。這引用由javap顯示為#2,是一個引用calc方法的符號,解析于從前面描述的常量池中。

        其它的新信息顯然是calc方法本身的代碼。它首先將第一個整數參數加載到操作數堆棧上(iload_0)。下一條指令,i2d,通過應用擴展轉換將其轉換為double類型。由此產生的double類型取代了操作數堆棧的頂部。

        再下一條指令將一個double類型常量2.0d(從常量池中取出)推到操作數堆棧上。然后靜態方法Math.pow調用目前為止準備好的兩個操作數值(第一個參數是calc和常量2.0d)。當Math.pow方法返回時,他的結果將會被存儲在其調用程序的操作數堆棧上。在下面說明。

        圖文詳解 Java 字節碼,想不懂都難!

        同樣的程序應用于計算Math.pow(b,2):

        圖文詳解 Java 字節碼,想不懂都難!

        下一條指令,dadd,會將棧頂的兩個中間結果出棧,將它們相加,并將所得之和推入棧頂。最后,invokestatic 對這個和值調用 Math.sqrt,將結果從 double(雙精度浮點型) 窄化轉換(d2i)成 int(整型)。整型結果會返回到 main 方法中, 并在這里保存到 c(istore_3)。

        創建實例

        現在修改這個示例,加入 Point 類來封裝 XY 坐標。

        圖文詳解 Java 字節碼,想不懂都難!

        編譯后的 main 方法的字體碼如下:

        圖文詳解 Java 字節碼,想不懂都難!

        這里引入了 new、dup 和 invokespecial 幾個新指令。new 指令與編程語言中的 new 運算符類似,它根據傳入的操作數所指定類型來創建對象(這是對 Point 類的符號引用)。對象的內存是在堆上分配,對象引用則是被推入到操作數棧上。

        dup指令會復制頂部操作數的棧值,這意味著現在我們在棧頂部有兩個指向Point對象的引用。接下來的三條指令將構造函數的參數(用于初始化對象)壓入操作數堆棧中,然后調用與構造函數對應的特殊初始化方法。下一個方法中x和y字段將被初始化。該方法完成之后,前三個操作數的棧值將被銷毀,剩下的就是已創建對象的原始引用(到目前為止,已成功完成初始化了)。

        接下來,astore_1將該Point引用出棧,并將其賦值到索引1所保存的本地變量(astore_1中的a表明這是一個引用值).

        圖文詳解 Java 字節碼,想不懂都難!

        通用的過程會被重復執行以創建并初始化第二個Point實例,此實例會被賦值給變量b。

        圖文詳解 Java 字節碼,想不懂都難!

        最后一步是將本地變量中的兩個Point對象的引用加載到索引1和2中(分別使用aload_1和aload_2),并使用invokevirtual調用area方法,該方法會根據實際的類型來調用適當的方法來完成分發。

        例如,如果變量a包含一個擴展自Point類的SpecialPoint實例,并且該子類重寫了area方法,則重寫后的方法會被調用。在這種情況下,并不存在子類,因此僅有area方法是可用的。

        圖文詳解 Java 字節碼,想不懂都難!

        請注意,即使area方法接受單參數,堆棧頂部也有兩個Point的引用。第一個(pointA,來自變量a)實際上是調用該方法的實例(在編程語言中被稱為this),對area方法來說,它將被傳遞到新棧幀的第一個局部變量中。另一個操作數(pointB)是area方法的參數。

        另一種方式

        你無需對每條指令的理解和執行的準確流程完全掌握,以根據手頭的字節碼了解程序的功能。例如,就我而言,我想檢查代碼是否驅動Java stream來讀取文件,以及流是否被正確地關閉。現在以下面的字節碼為例,確認以下情況是很簡單的:一個流是否被使用并且很有可能是作為try-with-resources語句的一部分被關閉的。

        public static void main(java.lang.String[]) throws java.lang.Exception;

        descriptor: ([Ljava/lang/String;)V

        flags: (0x0009) ACC_PUBLIC, ACC_STATIC

        Code:

        stack=2, locals=8, args_size=1

        0: ldc #2 // class test/Test

        2: ldc #3 // String input.txt

        4: invokevirtual #4 // Method java/lang/Class.getResource:(Ljava/lang/String;)Ljava/net/URL;

        7: invokevirtual #5 // Method java/net/URL.toURI:()Ljava/net/URI;

        10: invokestatic #6 // Method java/nio/file/Paths.get:(Ljava/net/URI;)Ljava/nio/file/Path;

        13: astore_1

        14: new #7 // class java/lang/StringBuilder

        17: dup

        18: invokespecial #8 // Method java/lang/StringBuilder."":()V

        21: astore_2

        22: aload_1

        23: invokestatic #9 // Method java/nio/file/Files.lines:(Ljava/nio/file/Path;)Ljava/util/stream/Stream;

        26: astore_3

        27: aconst_null

        28: astore 4

        30: aload_3

        31: aload_2

        32: invokedynamic #10, 0 // InvokeDynamic #0:accept:(Ljava/lang/StringBuilder;)Ljava/util/function/Consumer;

        37: invokeinterface #11, 2 // InterfaceMethod java/util/stream/Stream.forEach:(Ljava/util/function/Consumer;)V

        42: aload_3

        43: ifnull 131

        46: aload 4

        48: ifnull 72

        51: aload_3

        52: invokeinterface #12, 1 // InterfaceMethod java/util/stream/Stream.close:()V

        57: goto 131

        60: astore 5

        62: aload 4

        64: aload 5

        66: invokevirtual #14 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V

        69: goto 131

        72: aload_3

        73: invokeinterface #12, 1 // InterfaceMethod java/util/stream/Stream.close:()V

        78: goto 131

        81: astore 5

        83: aload 5

        85: astore 4

        87: aload 5

        89: athrow

        90: astore 6

        92: aload_3

        93: ifnull 128

        96: aload 4

        98: ifnull 122

        101: aload_3

        102: invokeinterface #12, 1 // InterfaceMethod java/util/stream/Stream.close:()V

        107: goto 128

        110: astore 7

        112: aload 4

        114: aload 7

        116: invokevirtual #14 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V

        119: goto 128

        122: aload_3

        123: invokeinterface #12, 1 // InterfaceMethod java/util/stream/Stream.close:()V

        128: aload 6

        130: athrow

        131: getstatic #15 // Field java/lang/System.out:Ljava/io/PrintStream;

        134: aload_2

        135: invokevirtual #16 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

        138: invokevirtual #17 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

        141: return

        ...

        可以看到java/util/stream/Stream執行forEach之前,首先觸發InvokeDynamic以引用Consumer。與此同時會發現大量調用Stream.close與Throwable.addSuppressed的字節碼,這是編譯器實現try-with-resources statement的基本代碼。

        這是完整的原始代碼。

        總結

        還好字節碼指令集簡潔,生成指令時幾乎少有的編譯器優化,反編譯類文件可以在沒有源碼的情況下檢查代碼,當然如沒有源碼這也是一種需求!

        感謝大家閱讀由Java教程分享的“圖文詳解 Java 字節碼,想不懂都難!”希望對大家有所幫助,更多精彩內容請關注Java培訓官網

        免責聲明:本文由小編轉載自網絡,旨在分享提供閱讀,版權歸原作者所有,如有侵權請聯系我們進行刪除

        預約申請免費試聽課

        填寫下面表單即可預約申請免費試聽!怕錢不夠?可就業掙錢后再付學費! 怕學不會?助教全程陪讀,隨時解惑!擔心就業?一地學習,可全國推薦就業!

        上一篇:SVN_Tortoisesvn 圖標介紹
        下一篇:Stack Overflow上受歡迎的程序員書籍
        12大要點讓你的Java開發所向披靡~

        12大要點讓你的Java開發所向披靡~

        學習Java最好的12本免費在線電子書

        學習Java最好的12本免費在線電子書

        Java常用日志框架介紹

        Java常用日志框架介紹

        一篇文章了解RPC框架原理

        一篇文章了解RPC框架原理

        • 掃碼領取資料

          回復關鍵字:視頻資料

          免費領取 達內課程視頻學習資料

        • 視頻學習QQ群

          添加QQ群:1143617948

          免費領取達內課程視頻學習資料

        Copyright ? 2021 Tedu.cn All Rights Reserved 京ICP備08000853號-56 京公網安備 11010802029508號 達內時代科技集團有限公司 版權所有

        選擇城市和中心
        貴州省

        福建省

        • 達內廈門軟件園中心
        廣西省

        海南省

        国产高清情侣视频2019年,限制电影福利在线观看,23伊人大香蕉 百度 好搜 搜狗
        <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <文本链> <文本链> <文本链> <文本链> <文本链> <文本链>