Java字节码解读
Java最重要的一個機制就是虛擬機JVM,通過執行編譯好的字節碼.class文件,可以實現跨平臺的可移植性,那么小伙伴們會不會好奇字節碼文件是怎樣的,它是怎么工作的?參考來源,為了便于回顧,特此按自己思路重頭來一遍,接下來我們就一起來解讀一下字節碼文件,前方高能,大腦請提前清空。
?
1. 前期準備
1.1 首先編寫一個測試程序,簡單地在main方法中聲明一個Integer包裝類型變量i和一個基本類型int類型變量n,代碼如下。
public class EnTest {public static void main(String[] args) {Integer i = 10;int n = i;} }1.2 命令行javac編譯得到.class字節碼文件
1.3 安裝二進制文件編輯器,博主使用Notepad++,可參照我的另一篇博客,你也可以使用其它編輯器。
?
2. 打開字節碼文件
2.1 使用安裝HexEditor插件的Notepad++打開字節碼文件,需要說明的是,文件中都是16進制,所以兩位剛好是一個字節,如下圖。
?
2.2 按照對照表分析字節碼文件
2.2.1 從文件取4個字節【ca fe ba be】得到第一塊內容,文件類型
最先的4個字節ca fe ba be代表了字節碼文件的類型,只要是class文件,開頭的四個字節就是cafebabe,音譯咖啡寶貝也就是java的logo熱騰騰的咖啡。
?
2.2.2接著從文件取2+2個字節【00 00 00 3b】?得到第二塊內容,java版本
其中開始的【00 00】這兩個字節的十進制是0,代表,次版本號是0,;然后【00 3b】的十進制是59,代表主版本號是15,所以我的java版本是java 15
?
2.2.3接著取2+n個字節得到第三塊內容——常量池
先取兩個字節00 19,化為十進制為25,表示有25項常量,但是第一項常量是系統預留,所以是24項常量,下圖是常量對應表,每一個常量由兩三項數據構成,但是每一項常量的第一個數據都是一個字節大小,所以接下來我們取一個字節。
?
2.2.3.1 第一項常量,取一個字節【0a】
0a十進制是10,根據值是10確定常量類型是CONSTANT_Methodref_info,它的第二項數據占兩個字節,是索引,表示指向第幾項常量。取接下來的兩個字節【00 02】十進制是2,表示指向第2項常量,現在還沒求第二項常量,看后面。然后第三項數據也是占兩個字節,也是索引,再取接下來的【00 03】十進制是3,表示指向第3項常量,現在還沒求第三項常量,第一項常量結束。
?
2.2.3.2第二項常量,取一個字節【07】
07十進制是7,根據7確定常量類型是CONSTANT_class_info,它的第二項數據占兩個字節,是索引,也表示指向第幾項常量,取接下來的兩個字節【00 04】十進制是4,指向第4項常量,第2項常量結束。
?
2.2.3.3第三項常量,取一個字節【0c】
0c十進制是12,根據值是12確定常量類型是CONSTANT_NameAndType_info,它的第二項數據占兩個字節,是索引,表示指向第幾項常量。取接下來的兩個字節【00 05】十進制是5,表示指向第5項常量。然后第三項數據也是占兩個字節,也是索引,再取接下來的【00 06】十進制是6,表示指向第6項常量,第3項常量結束。
?
2.2.3.4第四項常量,取一個字節【01】
01十進制是1,根據值是1確定常量類型是CONSTANT_Utf8_info,它的第二項數據占兩個字節,是長度length。取接下來的兩個字節【00 10】十進制是16,表示長度是16。然后第三項數據占一個字節,是長度為length=16的字符串,取接下來的【6a 61 76 61 2f 6c?61 6e 67 2f 4f 62 6a 65 63 74】轉化為十進制,然后根據ASCII碼轉化為一個個字符,字符串使用程序計算如下圖,得到“java/lang/Object”,第4項常量結束。
?
2.2.3.5第5項常量,取一個字節【01】?
01十進制是1,根據值是1確定常量類型是CONSTANT_Utf8_info,它的第二項數據占兩個字節,是長度length。取接下來的兩個字節【00 06】十進制是6,表示長度是6。然后第三項數據占一個字節,是長度為length=6的字符串,取接下來的【3c 69 6e ?69 74 3e 】轉化為十進制,然后根據ASCII碼轉化為一個個字符,字符串使用程序計算如下圖,得到“<init>”,第5項常量結束。
?
2.2.3.6第6項常量,取一個字節【01】?
01十進制是1,根據值是1確定常量類型是CONSTANT_Utf8_info,它的第二項數據占兩個字節,是長度length。取接下來的兩個字節【00 03】十進制是3,表示長度是3。然后第三項數據占一個字節,是長度為length=3的字符串,取接下來的【28?29 56】轉化為十進制,然后根據ASCII碼轉化為一個個字符,字符串使用程序計算如下圖,得到“()V”,第6項常量結束。
?
2.2.3.7?第7項常量,取一個字節【0a】
0a十進制是10,根據值是10確定常量類型是CONSTANT_Methodref_info,它的第二項數據占兩個字節,是索引,表示指向第幾項常量。取接下來的兩個字節【00 08】十進制是8,表示指向第8項常量,現在還沒求第8項常量,看后面。然后第三項數據也是占兩個字節,也是索引,再取接下來的【00 09】十進制是9,表示指向第9項常量,現在還沒求第9項常量,第7項常量結束。
?
……………………………………………………………………
……………………………………………………………………
?
以此類推可以得到其它17項常量,我就不一一推導了,使用javap -v EnTest命令得到反編譯內容得到常量池內容和常量池解讀結束位置如下圖。
?
2.2.4根據對照表第四塊內容訪問標志位,取兩個字節【00 21】
【00 21】表示0x0020和0x0001進行或操作得到,訪問屬性表如下圖所示,即表示類屬性是public和繼承了父類(默認繼承Object類)。
?
2.2.5根據對照表得到第5塊內容本類名稱,取兩個字節【00 11】
【00 11】十進制是17,表示此處從第17項常量獲得值,而且第17項常量指向第18項常量,第18項常量值為“EnTest”,代表本類名稱為“EnTest”。
?
2.2.6根據對照表得到第6塊內容父類名稱,取兩個字節【00 11】
【00 02】十進制是2,此處從第2項常量獲得值,而且第2項常量指向第4項常量,第4項常量值為“java/lang/Object”,代表父類名稱為“java/lang/Object”。
?
2.2.7根據對照表得到第7塊內容接口信息,先取兩個字節【00 00】
【00 00】表示接口數量為0,第7塊內容結束。
?
2.2.8根據對照表得到第8塊內容類級別變量信息,先取兩個字節【00 00】
該處描述類和接口的變量,不包括方法中的局部變量,我們只定義了兩個變量,所以此處為0,如果有類級別變量,可以安裝下圖字段表進行解析。
?
2.2.9根據對照表得到第9塊內方法信息,先取兩個字節【00 02】
?
【00 02】十進制是2,表示有兩個方法,但我們只有一個main方法,我們接下來根據方法表來推斷一下另一個方法。
先取兩個字節【00 01】,該處是訪問標志符,根據訪問屬性表可知0x0001表示public,其次取兩個字節【00 05】十進制是5,表示從常量池取第5項常量值得到方法名“<init>”,然后取兩個字節【00 06】十進制是6,表示從常量池取第6項常量值得到方法描述"()V",接著取兩個字節【00 01】十進制是1,表示有一個屬性,需要根據屬性表進行解析,屬性表如下圖。
先取兩個字節表示屬性名字在常量池的索引,取兩個字節【00 13】十進制是19,第19項的值是“Code”,所以屬性名稱是“Code”,既然是Code屬性,虛擬機就會需要調用Code屬性表,不再使用上述屬性表結構解讀。
由于Code屬性表第二項的屬性長度要4個字節,則取4個字節【00 00 00 1d】十進制是29,長度 是29,所以往后29個字節都是該Code屬性的內容。接下來取兩個字節【00 01】表示max_stack=1,再取兩個字節【00 01】表示max_locals=1,再取4個字節【00 00 00 05】表示字節碼指令長度是5,接下來5個字節【2a b7 20 01 b1 ?】具體意思就不展開了,由于后續解析內容涉及更多屬性表內容,但是平時又不大涉及,故不多多講解,到此結束解讀。
?
?
最終使用javap -v EnTest反編譯的部分字節碼內容如下。
?
?
總結
- 上一篇: 二进制、八进制、十六进制和十进制的相互转
- 下一篇: 到底什么是面向对象,面试中怎么回答。面向