一、如何查看一個類文件的16進制結構
- 寫一個簡單的java程序,javac編譯產生.class文件
- 用notepad++打開,一開始可能是亂碼引入插件HexEditor
HexEditor.dllx64版本下載
將HexEditor.dll
文件放在Notepad++的plugins文件夾下面,重新打開文件
旁邊會多出一個H字樣點擊之後就可以查看.class文件的16進制的格式了
程序代碼:
public class Test{
private int m;
public int inc(){
return m+1;
}
}
二、文件結構
Class文件格式
其中u1,u2,u3表示1個字節,2個字節,3個字節
而類型帶有“-info”的是有層次關係的複合結構數據。有點類似於結構體。
1.魔數(Magic Number)和版本(Minor Version、Major Version)
魔數:用於確定這個文件是否是一個被虛擬機接受的class文件。
很多文件存儲標準都有魔數,使用魔數辨別文件類別比後綴名更加安全,後綴名可以輕易的改變,而魔數不能例如下面的JPG文件:
然後將其後綴名改爲GIF:
魔數不會改變。這個值可以由文件制定者指定,沒被廣泛使用且不會引起混淆即可。
java的魔數cafebabe,咖啡寶貝hhh
版本號:將0x0034轉爲10進制就是52,下表中只記錄了JDK1.7,而我是JDK1.8
JDK是向下兼容,即1.7的可以用1.6編譯產生的Class文件,而不能用1.8編譯產生的。
2.常量池(constant_pool)
常量池長度不固定,所以入口放置一項u2類型來指明有多少項常量
0x0013->19。表明有18項常量,爲什麼不是19項呢?
爲了滿足後面某些指向常量池的索引值的數據在特定的情況下表達“不應用任何一個常量池項目”
然後第一項常量0a->10記爲#1
,這裏又要查表了:
10是一個符號引用,然後在查表:
後面兩個都是索引向,指向#4
和#15
,而4和15 我們還沒有讀到,就繼續往下讀
第二個0x09->9,項目類型表可知爲字段引用符號。然後一直這樣讀下去,當有01類型時,就將數字對應的ASCII碼記錄下來,這就是我們能夠讀懂的地方了。
是不是很麻煩?而且很機械?既然知道怎麼讀了,當然是有程序可以幫你翻譯的,但是還是要先搞清楚原理嘛
在cmd命令行輸入
javap -verbose Test//Test爲開始編譯的文件名
獲得以下結果:
常量池部分:
3.訪問標誌(access_flags)
用於識別一些類的或者接口層次的訪問信息,包括是類還是接口,是不是public等等
如果是則將標誌位做 | 運算,如果不是則不管,該類就應該是0x0001|0x0020 = 0x0021:
4.類索引(this_class),父類索引(super_class)與接口(interfaces)集合
這三項數據確定了這個類的繼承關係。
除了java.lang.Object外所有java類的父類都不爲0,因爲object是所有類的粑粑。
接口索引集合大小爲0,沒有接口。
5.字段表集合(filed_info)
用於描述接口或者類中聲明的變量
緊跟着接口集合後面的位容量計數器
本測試文件中的值位0x0001,即只有一個字段表數據
標誌位0x0002–>ACC_PRIVATE爲真,簡單名稱0x0005指向#5
,字段和方法描述符0x0006指向#6
,
標識符 | 含義 | 標識符 | 含義 |
---|---|---|---|
B | 基本類型byte | J | 基本類型long |
C | 基本類型char | S | 基本類型short |
D | 基本類型double | Z | 基本類型boolean |
F | 基本類型float | V | 特殊類型void |
I | 基本類型int | L | 對象類型。如Object |
在看我們寫的程序是:
private int m;
是不是就能理解了呢
6.方法表集合
其方法和字段表一樣。
唯一不同的就是訪問標誌和屬性表集合的可選項。圖中我們可以解讀出方法的信息,但是卻沒有執行的代碼。這是因爲經過編譯器編譯成字節碼指令後存儲在方法的屬性集合中一個名爲“Code”屬性裏面,也就是0x0009這個地方
7.屬性表(attribute_info)集合
現在預定義的屬性已經增加到了21個
屬性表的結構
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u1 | info | attribute_length |
- Code屬性
前面說過編譯器編譯成字節碼指令後存儲在方法的屬性集合中一個名爲“Code”屬性裏面。但接口或者抽象類就不存在Code屬性
attribute_name_index是一個指向指向utf8型常量的索引,常量值固定爲“Code”。
由於屬性名和屬性長度一共爲6字節,所以屬性值的長度位整個屬性表的長度減去6。
max_stack代表操作數棧深度的最大值。虛擬機運行時根據這個值來分配棧幀。
max_locals代表局部變量表所需的存儲空間。單位是Slot
Slot是虛擬機爲局部變量分配內存所要使用的最小單位。對於byte、char、float、int、short、boolean 和returnAddress等長度不超過32的佔用1個Slot,double和long佔用2個,且局部變量表中的Slot可以重用
code_length代表字節碼的長度。雖然他是一個u4類型,但實際長度只用了u2。如果超過了javac會拒絕編譯。但有些時候複雜的JSP文件用某些JSP編譯器會將JSP頁面的內容和輸出信息歸到一個方法之中,從而導致編譯失敗
看看代碼
public class Test{
private int m;
public int inc(){
return m+1;
}
}
初始化方法init()
和inc()
都沒有參數但是Args_size()
都爲1。這是因爲我們常用的this
就是這個參數。實例方法的局部變量表中至少會存在一個指向當前對象實例的局部變量,局部變量表中也會預留出第一個Slot位來存放對象實例的引用