一文帶你深入瞭解 Java 字節碼

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"1.1 什麼是字節碼?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"Java 在剛剛誕生之時曾經提出過一個非常著名的口號: “","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"一次編寫,到處運行(write once,run anywhere)","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"”,這句話充分表達了軟件開發人員對沖破平臺界限的渴求。“與平臺無關”的理想最終實現在操作系統的運用層上: 虛擬機提供商開發了許多可以運行在不同平臺上的虛擬機,這些虛擬機都可以載入和執行同一種平臺無關的字節碼,從而實現了程序的“一次編寫到處運行”。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"各種不同平臺的虛擬機與所有平臺都統一使用的程序存儲格式—","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"字節碼(ByteCode)","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":",因此,可以看出字節碼對 Java 生態的重要性。之所以被稱爲字節碼,是因爲字節碼是由十六進制組成的,而 JVM(Java Virtual Machine)以兩個十六進制爲一組,即以字節爲單位進行讀取。在 Java 中使用 ","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"javac","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" 命令把源代碼編譯成字節碼文件,一個 .java 源文件從編譯成 .class 字節碼文件的示例如圖 1 所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/35/356c861ca7ebb655d3911a0b4c1cd0f7.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"圖 1","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"對於從事基於 JVM 對於語言的開發人員來說,比如: Java,瞭解字節碼可以更準確、更直觀的理解 Java 語言中有更深層次的東西,比如通過字節碼,可以很直觀的看到 ","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"volatile","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" 關鍵字如何在字節碼上生效。另外,字節碼增強技術還有各種 ORM 框架、Spring AOP、熱部署等一些應用中經常使用,深入理解其原理對於我們來說大有裨益。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"由於 JVM 規範的存在,只要最終生成了符合 JVM 字節碼規範的文件都可以在 JVM 上運行,因此,這個也給其它各種運行在運行, JVM 上的語言(如: Scala、Groovy、Kotlin)提供了一個機會,可以擴展 Java 沒有實現的特性或者是實現一些語法糖。接下來就讓我們一起看看這個字節碼文件結構到底是什麼樣的。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"1.2 Java 字節碼結構","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"Java 源文件通過用 ","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"javac","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" 命令編譯後就會得到 .class 結尾的字節碼文件,比如一個簡單的 ","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"JavaCodeCompilerDemo","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" 類如圖 2 所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0d/0d38bdee499d26d8d3d42657619f70e8.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"圖 2","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"編譯後生成的 .class 字節碼文件,打開後是一堆文件 ","attrs":{}},{"type":"text","text":"十六進制","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" 數,如圖 3 所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e6/e62aef06778d343f9dc9b7f2fb56a68c.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"圖 3","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"在上節提過,JVM 對於字節碼規範是有要求的,打開編譯後的字節碼文件看似混亂無章,其實它是符合一定的結構規範的,JVM 規範要求每一個字節碼文件都要由十部分固定的順序組成的,接下來我們將一一介紹這部分,整體的組成結構如圖 4 所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fa/fa37a015e5fa5343a899989e4bbbe3dd.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"圖 4","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"(1)魔數(Magic Number)","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" 每個字節碼文件的頭 4 個字節稱爲 ","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"魔數(Magic Number)","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":",它的唯一作用是確定這個文件是否爲一個能被虛擬機接受的 Class 文件。很多文件存儲標準中都使用魔數來進行身份識別,譬如圖片格式,如 gif 或者 jpg 等在文件中都存有魔數。使用魔數而不是擴展名來進行識別主要是基於安全方面的考慮,因爲文件擴展名可以隨意改動。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"魔數的固定值爲: ","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"0xCAFEBABE","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":",魔數放在文件頭,JVM 可以根據文件的開頭來判斷這個文件是否可能是一個字節碼文件,如果是,纔會進行之後的操作。","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#666666","name":"user"}}],"text":"有趣的是,魔數的固定值是 Java 之父 James Gosling 制定的,爲 CafeBabe(咖啡寶貝),而 Java 的圖標爲一杯咖啡。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"(2)版本號(Version)","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" 版本號爲魔數之後的 4 個字節,前兩個字節表示次版本號(Minor Version),後兩個字節表示主版本號(Major Version),上圖 3 中版本號爲: “00 00 00 34”,次版本號轉化爲十進制爲 0,主版本號轉化爲十進制 52(3 * 16^1 + 4 * 16^0 = 52),在 Oracle 官網中查詢序號 52 對應的 JDK 版本爲 1.8,所以編譯該源代碼文件的 Java 版本爲 1.8.0。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"(3)常量池(Constant Pool)","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" 緊接着主版本號之後的字節是常量池入口。常量池中存儲兩種類型常量: 字面量和符號運用。字面量爲代碼中聲明爲 ","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"final","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" 的常量值,符號引用如類和接口的全侷限定名、字段的名稱和描述符、方法的名稱和描述符。常量池整體上分爲兩部分: 常量池計數器和常量池數據區,如圖 5 所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/dd/ddfcffd22b6c3a116bb409c2aedd7230.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"圖 5","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"常量池計數器(constant_pool_count)","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":": 由於常量池的數量不固定,所以需要先放置兩個字節來表示常量池容量計數值,圖 2 示例代碼的字節碼的前十個字節如下圖 6 所示,將十六進制的 17 轉爲十進制的值爲 33 (1 * 16^1 + 7 * 16^0 = 33),排除下標 0,也就是說這個類文件裏有 32 個常量。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/46/4653858455dd22a298d26c9c660d106d.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"圖 6","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"常量池數據區","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":": 數據區是由(constant_pool_count - 1)個 cp_info 結構組成,一個 cp_info 的結構對應一個常量。在字節碼中共有 14 種類型的 cp_info 每種類型的結構都是固定的,如圖 7 所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d0/d00db021d496c415f7f7a5106eb7a16d.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"圖 7","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"以 CONSTANT_Utf8_info 以它爲例,它的結構如表 1 所示:","attrs":{}}]},{"type":"embedcomp","attrs":{"type":"table","data":{"content":"

名稱

長度

tag

1 字節

01 對應圖 7 中 CONSTANT_Utf8_info 的標誌欄中的值

length

2 字節

該 utf8 字符串的長度

bytes

length 字節

length 個字節的具體數據

"}}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"表 1 首先第一個字節 tag,它的取值對應圖 7 中的 Tag,由於它的類型是 CONSTANT_Utf8_info,所以值爲 01(十六進制)。接下來兩個字節標識該字符串的長度 length,然後 length 個字節爲這個字符串具體的值。從圖 3 的字節碼中摘取一個 cp_info 結構,將它翻譯過來後,其含義爲: 該常量爲 utf8 字符串,長度爲 7 字節,數據爲: numberA,如圖 8 所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b6/b6d1eeb36273ffd91439eed83ddf4a08.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"圖 8","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"其它類型的 cp_info 結構在本文不再細說,和 CONSTANT_Utf8_info 結構大同小異,都是先通過 tag 來標識類型,然後後續的 n 個字節來描述長度和數據。等我們對這些結構比較瞭解了之後,我們就可以通過: ","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"javap -verbose JavaCodeCompilerDemo","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" 命令查看 JVM 反編譯後的完整常量池,可以看到反編譯結果可以將每一個常量池 cp_info 結構的類型和值都很明確地呈現出來,如圖 9 所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/34/34101d08c67c9a3b776416571dea44d2.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"圖 9","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"(4)訪問標誌(access_flag)","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" 常量池結束之後的兩個字節,描述了該 Class 是類還是接口,以及是否被 ","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"Public","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"、","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"Abstract","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"、","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"Final","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" 等修飾符修飾。JVM 規範規定了如下表 2 所示的 9 種訪問標誌。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"需要注意的是,JVM 並沒有窮舉所有的訪問標誌,而是使用 按位或 操作來進行描述的,比如某個類的修飾符爲 public final,則對應的訪問修飾符的值爲 ACC_PUBLIC | ACC_FINAL,即 ","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"0x0001 | 0x0010 = 0x0011","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"。","attrs":{}}]},{"type":"embedcomp","attrs":{"type":"table","data":{"content":"

標誌名稱

標誌值

含義

ACC_PUBLIC

0x0001

字段是否爲 public

ACC_PRIVATE

0x0002

字段是否爲 private

ACC_PROTECTED

0x0004

字段是否爲 protected

ACC_STATIC

0x0008

字段是否爲 static

ACC_FINAL

0x0010

字段是否爲 final

ACC_VOLATILE

0x0040

字段是否爲 volatile

ACC_TRANSIENT

0x0080

字段是否爲 transient

ACC_SYNCHETIC

0x1000

字段是否爲編譯器自動產生

ACC_ENUM

0x4000

字段是否爲 enum

"}}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"表 2","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"(5)當前類名(this_class)","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" 訪問標誌後的兩個字節,描述的是當前類的全限定名。這兩個字節保存的值爲常量池中的索引值,根據索引值就能在常量池中找到這個類的全限定名。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"(6)父類名稱(super_class)","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" 當前類名的後兩個字節,描述父類的全限定名。這兩個字節保存的值也是在常量池中的索引值,根據索引值就能在常量池中找到這個類的父類的全限定名。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"(7)接口信息(interfaces)","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" 父類名稱後的兩個字節,描述這個類的接口計數器,即: 當前類或父類實現的接口數量。緊接着的 n 個字節是所有的接口名稱的字符串常量在常量池的索引值。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"(8)字段表(field_table)","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" 字段表用於描述類和接口中聲明的變量,包含類級別的變量以及實例變量,但是不包含方法內部聲明的 局部變量。字段表也分爲兩部分,第一部分是兩個字節,描述字段個數,第二部分是每個字段的詳細信息 ","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"field_info","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"。字段表結構如圖 10 所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ef/ef9f0eb6b2f78c0e395c0b15806be5be.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"圖 10","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"以圖 3 中的字節碼字段表爲例,如下圖 11 所示。其中字段的訪問標誌查表 2,002 對應爲 Private,通過索引下標在圖 9 常量池分別得到的字段名爲: numberA,描述符爲: I(在JVM 中的I代表 Java 中的 int)。綜上,就可以唯一確定出類別。 ","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"JavaCodeCompilerDemo","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" 聲明的變量爲: ","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"private int numberA","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" 。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/68/6891a98a0578f7c6d57c783d94708f05.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"圖 11","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"(9)方法表(method_table)","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" 字段表結束後爲方法表,方法表也是由兩部分組成,第一部分爲兩個字節描述方法的個數,第二個部分爲每個方法的詳細信息。方法的詳細信息包括:方法的訪問標誌、方法名、方法的描述符以及方法的屬性,如圖 12 所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fb/fbb31c646c1b73fe93f4223886eb74a5.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"圖 12","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"方法的權限修飾符依然可以通過圖 9 的索引值查詢到,方法名和方法的描述符都是常量池的索引值,可以通過索引值在常量池中查詢得到。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"而方法屬性這個部分比較複雜,我們可以藉助 ","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"javap -verbose","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" 將其反編譯爲人們可讀的信息進行解讀。如圖 13 所示。我們可以看到屬性中包含三個部分:","attrs":{}}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"Code 區","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":": 源代碼對應的 JVM 指令操作碼,我們在字節碼增強的時候重點操作的就是這個部分。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"LineNumberTable","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":": 型號表,將 Code 區的操作碼和源代碼的行號對應,Debug 時會起到作用(即: 當源代碼向下走一行,相應的需要走幾個 JVM 指令操作碼)。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"LocalVariableTable","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":": 本地變量表,包含 this 和局部變量,之所以可以在每一個非 static 的方法內部都可以調用到 this,是因爲 JVM 將 this 作爲每個方法的第一個參數隱式進行傳入。","attrs":{}}]}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c3/c3a34b807594ce4abdfb05e93cce0a9e.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"圖 13","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"(10)附加屬性表(additional_attribute_table)","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" 字節碼的最後一部分,存放了在文件中類或接口所定義的屬性的基本信息。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"1.3 Java 字節碼操作集合","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"在圖 13 中,Code 區的編號是 0 ~ 10,就是 .java 源文件的方法源代碼編譯後讓 JVM 真正執行的操作碼。爲了幫助人們理解,反編譯後看到的是十六進制操作碼所對應的助記符,十六進制值操作碼和助記符的對應關係,以及每個操作碼的具體作用可以查看 Oracle 官網,在需要的時候查閱即可。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"比如上圖 13 的助記符爲 iconst_2,對應圖 3 中的字節碼 0x05,作用是將 int 值 2 壓入操作數棧中。以此類推,對 0 ~ 10 的助記符理解後就是整個 ","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"sum()","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" 方法的操作數碼實現。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"1.4 查看字節碼工具","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"如果我們每次反編譯都要使用 ","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"javap","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" 命令的話,確實比較繁瑣,這裏我推薦大家一個 IDEA 插件: jclasslib。使用效果如圖 14 所示: 代碼編譯後在菜單欄: ","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"View","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":" -> ","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong","attrs":{}}],"text":"Show Bytecode With jclasslib","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":",可以很直觀地看到當前字節碼文件的類信息、常量池、方法區等信息,非常方便。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/cd/cd9b0c1a6d2ceac332ff4dce91a14f21.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"圖 14","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"1.5 總結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"Java 中字節碼文件是 JVM 執行引擎的數據入口,也是 Java 技術體系的基礎構成之一。瞭解字節碼文件的組成結構對後面進一步瞭解虛擬機和深入學習 Java 有很重要的意義。本文較爲詳細地講解了字節碼文件結構的各個組成部分,以及每個部分的定義、數據結構和使用方法。強烈建議自己動手分析一下,會理解得更加深入。如","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#222226","name":"user"}}],"text":"覺得有幫助,歡迎轉發、分享,我將更有動力堅持原創分享!","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章