<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          淺談 JVM 2:如何閱讀字節(jié)碼

          共 4868字,需瀏覽 10分鐘

           ·

          2022-01-20 21:46

          上文聊到理解字節(jié)碼和 JVM 執(zhí)行過(guò)程能夠幫助我們?nèi)粘i_發(fā)中解決疑難雜癥。這次,我們先來(lái)看看如何閱讀字節(jié)碼。

          字節(jié)碼文件是我們使用?javac xxx.java?編譯而來(lái)的?xxx.class?文件,內(nèi)容為 8 bit 字節(jié)流。class?中的數(shù)據(jù)類型有?u1、u2?和?u4u1?代表該數(shù)據(jù)占用 1 字節(jié),u2?代表 2 字節(jié),以此類推。

          每個(gè)?class?文件包含了一個(gè)類、接口、模塊的定義。這些字節(jié)流所代表的內(nèi)容結(jié)構(gòu)由一個(gè)類似 C 語(yǔ)言結(jié)構(gòu)體?structure?的數(shù)據(jù)結(jié)構(gòu)來(lái)定義,該定義在虛擬機(jī)規(guī)范中給出,結(jié)構(gòu)如下。結(jié)合該結(jié)構(gòu)我們即可對(duì)照?class?字節(jié)流反編譯出該類的內(nèi)容。

          摘錄自?Java Virtual Machine Specification[1]

          ClassFile {    u4             magic;    u2             minor_version;    u2             major_version;    u2             constant_pool_count;    cp_info        constant_pool[constant_pool_count-1];    u2             access_flags;    u2             this_class;    u2             super_class;    u2             interfaces_count;    u2             interfaces[interfaces_count];    u2             fields_count;    field_info     fields[fields_count];    u2             methods_count;    method_info    methods[methods_count];    u2             attributes_count;    attribute_info attributes[attributes_count];}

          1. 示例代碼

          本次我們就以簡(jiǎn)單的?Demo.java?來(lái)介紹閱讀字節(jié)碼流程和如何理解字節(jié)碼。請(qǐng)先查看以下代碼,

          public class Demo {
          private int mThisIsInt = 1024;
          public static void main(String[] args) { System.out.println("hello world"); }
          private int getThisIsInt() { return mThisIsInt; }}

          2. 編譯為?class?字節(jié)碼

          執(zhí)行?javac Demo.java?即可得到?Demo.class?文件。這是我們本次要閱讀分析的目標(biāo)文件。

          3. 解讀字節(jié)碼

          使用?hexdump

          VSCode?中提供了?hexdump?插件,展示字節(jié)碼的 16 進(jìn)制數(shù)據(jù)。安裝該插件后,在?VSCode?中我們右鍵反編譯出的?Demo.class?文件,選擇?Show Hexdump?選項(xiàng),即可看到如下內(nèi)容。

          我們可以開始對(duì)照上文中的?ClassFile Structure?來(lái)解讀該內(nèi)容。

          magic?部分

          ClassFile?結(jié)構(gòu)告訴我們,第 1 部分內(nèi)容為?magic,u4?代表占用四字節(jié),對(duì)應(yīng)?CAFEBABE?內(nèi)容,這部分內(nèi)容用來(lái)標(biāo)識(shí) Java 字節(jié)碼文件格式,可以看到 Java 圖標(biāo)的源頭在此處。

          version?部分

          第 2、3 部分內(nèi)容為?minor_version?和?major_version,分別占用 2 字節(jié),對(duì)應(yīng)?0000?和?003A?部分內(nèi)容。代表此?class?文件主要版本為 58,次要版本為 0。此版本標(biāo)識(shí)和 JDK 版本相關(guān)聯(lián),具體可參考?Java Virtual Machine Specification[2]?表 4.1-A。

          constant_pool?部分

          第 4、5 部分內(nèi)容為?constant_pool_count?和?constant_pool。代表常量池相關(guān)內(nèi)容,constant_pool_count?標(biāo)明常量池?cái)?shù)量,等于?constant_pool?中的 item 數(shù)量。constant_pool?則用于存儲(chǔ)每個(gè)常量。其結(jié)構(gòu)為?cp_info。內(nèi)容如下所示。即由兩部分組成,1 字節(jié)的?tag,代表?cp_info?類型,比如?7?代表?Class——類,9?代表?Fieldref——字段。

          cp_info {    u1 tag;    u1 info[];}

          在此以?Methodref?為例。一個(gè)方法的結(jié)構(gòu)描述如下。

          CONSTANT_Methodref_info {    u1 tag;    u2 class_index;    u2 name_and_type_index;}

          ?tag?為?10,代表?Methodref?類型;?class_index?是指向常量池中?CONSTANT_Class_info?類型數(shù)據(jù)的索引,代表當(dāng)前方法屬于哪個(gè)類;?name_and_type_index?是指向常量池中?CONSTANT_NameAndType_info?類型數(shù)據(jù)的索引,代表方法的名稱和描述符,描述符則描述了方法的參數(shù)和返回值類型。

          這時(shí)直接閱讀字節(jié)碼 16 進(jìn)制數(shù)據(jù)已變得繁瑣。在此,我們借用另外一個(gè)工具,來(lái)進(jìn)一步解釋常量池和其索引的方式。

          使用?javap

          執(zhí)行?javap -v -p Demo.class?獲取到可讀性更強(qiáng)的反編譯文件。我們先查看一下?Constant pool?中的部分內(nèi)容。

          // 省略內(nèi)容...
          Constant pool: #1 = Methodref #2.#3 // java/lang/Object."":()V #2 = Class #4 // java/lang/Object #3 = NameAndType #5:#6 // "":()V
          // 省略內(nèi)容...

          常量池中使用數(shù)組保存每個(gè)常量的內(nèi)容。每個(gè)常量都有自己的索引,javap?反編譯出的文件中使用?#n?表示。

          #1?代表一個(gè)?Methodref?方法類型常量,該常量通過(guò)引用?#2?#3?號(hào)常量表示自己。

          #2?表示?Object?類。

          #3?使用?#5、#6?號(hào)常量表示自己。#5?#6?都為?CONSTANT_Utf8?類型,數(shù)據(jù)內(nèi)容分別為??和?()V。代表該方法的名稱為?,沒有參數(shù),返回值類型為?void。

          由此我們可以得出以下結(jié)構(gòu)。

          methods?部分

          之后的?access_flags、this_class?等部分,分別表示該類的訪問(wèn)標(biāo)識(shí)、父類、接口、字段等信息,解讀方法和常量池類似,不再詳細(xì)列出。

          值得一看的是,method_info?部分。其中包含了類中所有的方法。方法的描述和前文一致,其中的?attributes?屬性結(jié)構(gòu)如下。

          attribute_info {    u2 attribute_name_index;    u4 attribute_length;    u1 info[attribute_length];}

          我們方法的代碼部分,就在此存儲(chǔ)。其?attribute_name_index?指向內(nèi)容為?Code?的常量,代表此為代碼屬性。attribute_length?為屬性長(zhǎng)度,之后的?info?數(shù)組則存儲(chǔ)了方法代碼對(duì)應(yīng)字節(jié)碼。我們以 Java 代碼中?getThisIsInt?方法為例,其 Java 源代碼和對(duì)應(yīng)字節(jié)碼如下。

          // Java 代碼private int getThisIsInt() {    return mThisIsInt;}
          // 字節(jié)碼private int getThisIsInt();descriptor: ()Iflags: (0x0002) ACC_PRIVATECode: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #7 // Field mThisIsInt:I 4: ireturn LineNumberTable: line 11: 0

          我們重點(diǎn)關(guān)注?Code?部分。

          stack=1?表示操作數(shù)棧的最大深度為 1。

          locals=1?表示局部變量表的最大槽數(shù)為 1。

          args_size=1?表示方法參數(shù)的數(shù)量為 1。需要注意的是,getThisIsInt?方法沒有顯示的參數(shù)。但是該方法為實(shí)例方法,內(nèi)部可以直接訪問(wèn)?this,因此翻譯為字節(jié)碼時(shí),默認(rèn)添加了?this?參數(shù)提供方法內(nèi)訪問(wèn)當(dāng)前對(duì)象的功能。也就是說(shuō),方法內(nèi)可以訪問(wèn)?this?是通過(guò)給方法添加默認(rèn)參數(shù)實(shí)現(xiàn)的。

          0: aload_01: getfield #7 // Field mThisIsInt:I4: ireturn

          之后便是代碼對(duì)應(yīng)的字節(jié)碼,這部分實(shí)現(xiàn)了加載類字段?#7?號(hào)常量然后返回的功能。具體的執(zhí)行流程涉及 JVM 指令集,限于篇幅我們以后有時(shí)間再來(lái)具體聊聊。

          其中行首的?n:?代表字節(jié)碼偏移,等于當(dāng)前指令之前所有指令占用的存儲(chǔ)大小。比如?getfield #7?之前的?aload_0?指令占用 1 字節(jié),所以其偏移為 1。

          LineNumberTable?部分則是字節(jié)碼偏移和 Java 源代碼的對(duì)應(yīng)關(guān)系。line 11: 0?表示,Java 源代碼第 11 行?return mThisIsInt;?對(duì)應(yīng)代碼字節(jié)碼的 0 號(hào)偏移?aload_0?開始的字節(jié)碼部分。

          使用 jclasslib

          除了?hexdump?和?javap?之外,我們還可以使用?jclasslib?工具來(lái)閱讀字節(jié)碼。使用起來(lái)更加直觀和便捷。比如提供了常量池索引間跳轉(zhuǎn)、查看 JVM 規(guī)范、替換操作碼等功能。

          jclasslib?既提供了獨(dú)立的 app 也提供了 IDEA 插件。點(diǎn)擊查看?jclasslib Github[3]?和?IDEA 插件[4]。下圖為 IDEA 插件截圖。

          總結(jié)

          本次我們了解了如何結(jié)合 JVM 規(guī)范中的?ClassFile Structure,同時(shí)使用?hexdumpjavap、jclasslib?工具來(lái)解讀 Java 字節(jié)碼。

          解讀過(guò)程中,也結(jié)合字節(jié)碼,了解到方法中訪問(wèn)?this?的實(shí)現(xiàn)方式。

          對(duì)于 JVM 指令集和?Code?執(zhí)行流程,由于篇幅原因我們有時(shí)間再來(lái)聊。

          References

          [1]?Java Virtual Machine Specification:?https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-4.html
          [2]?Java Virtual Machine Specification:?https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-4.html
          [3]?jclasslib Github:?https://github.com/ingokegel/jclasslib
          [4]?IDEA 插件:?https://plugins.jetbrains.com/plugin/9248-jclasslib-bytecode-viewer


          瀏覽 101
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  台湾 无码 | 北条麻妃操逼片 | 国产精品一区二区在线播放 | 青娱乐在线视频观看 | 免费观看三区视频 |