最近在研究Java的反射和動態代理,發現使用這兩個Java神器須要瞭解.class文件的字節碼。因而翻閱了相關資料,在這篇博客中進行一番整理,也做爲本身學習的記錄。java
Java的可移植性是基於.java文件編譯後造成的惟一的字節碼文件.class文件能夠在不一樣操做系統上的jvm運行的機制。.class文件是一組以8位字節爲基礎單位的二進制流,各個數據項目嚴格按照順序緊湊的排列在.class文件中,中間沒有任何分隔符。程序員
當程序員編譯了.java文件後,在指定的路徑下會生成一個.class文件,使用editplus能夠直接以Hex viewer的格式打開.class文件編程
ClassTest.java數組
package com.classloader; public class ClassTest { public static void main(String[] args) { System.out.println("Hello,World!"); } }
ClassTest.classjvm
要想能讀懂class文件這種生肉乾貨,首先要理解.class文件中的一些基本概念工具
無符號數是一種基本的數據類型,一般用u1,u2,u4,u8表明一、二、四、8個字節的無符號數。
無符號數是用來描述數字、索引引用、數量值或者UTF-8編碼的字符串值,能夠稱做是.class文件的基本組成單位學習
表是由多個無符號數或其餘表構成的複合數據類型,整個.class文件的本質就是一個表。(表大都習慣性的以_info結尾)this
不管是無符號數,仍是表,當須要描述同一類型但數量不定的多數據的時候,常常會使用一個位置的容量計數器加若干個連續的數據項的形式。編碼
每一個.class文件的頭四個字節被稱爲「魔數」,其做用是肯定該.class文件是否爲一個能被HOTSPOT虛擬機接收的.class文件操作系統
魔數後面的四個字節是版本號,第五和第六個字節是「次版本號」,第七和第八個字節是「主版本號」。
Java的版本號是從45開始的,自jdk1.1以後的每一個jdk大版本發佈的主版本號都向上+1,而且高版本的jdk能向下兼容之前版本的.class文件。(注意:hex viewer下,.class文件中的數字都是16進制數)
在主版本號後面的是常量池入口,常量池是.class文件結構中與其餘項目關聯最多的數據類型,也是佔用.class文件空間最大的數據項目之一。
因爲常量池中的常量的數量是不固定的,因此常量池的入口須要放置一項u2類型的數據,表明常量池容量計數。這個容量計數是從1開始的(有別於傳統的程序員計數法則)。
上圖中的.class文件的常量池計數是34,因爲從1開始,因此常量的個數是33(十六進制的22是十進制的34)。也就是說,從計數位以後的33個表,都是表示常量的。
常量池中主要存放兩大類常量:字面量(literal)和字符引用(Symbolic References)。
字面量比較接近於Java語言層的常量概念,例如文本字符串、被聲明爲final的常量值等等。
字符引用包括三類變量:類和接口的全限定名、字段的名稱和描述符、方法的名稱和描述符。
剛剛提到過,常量池中的每個常量都是一個表,一共11種結構各不相同的表,這些表都有一個共同的特色,開始的第一位是一個u1類型的標誌位,表明當前這個常量屬於哪一種類型。(具體查看【查閱表格】)
總而言之,查看常量的方法就是:
1.第一個字節爲tag 查看常量池類型表找到對應的類型
2.找到對應結構的表,找到tag以後屬於常量的其餘無符號數
常量池結束後,緊接着的兩個字節表示訪問標誌(access_flags)。這個標誌用來識別一些類或接口層次的訪問信息,包括:這個classs是實體類仍是接口;是否認義爲public;是不是抽象類;是否爲final類等等。
具體訪問標誌的映射詳見【查閱表格】
類引索(this_class)和父類引索(super_class)都是一個u2類型的數據,接口引索集合是一組u2類型的數據集合。
在訪問標誌以後,緊接着是類引索、父類引索,共佔據4個字節。他們各自指向一個類型爲CONSTANT_Class_info的類描述符常量,經過CONSTANT_Class_info類型的常量中的索引值能夠找到定義在CONSTANT_Utf8_info類型常量中的全限定名字符串。
在接口索引集合後的兩個字節是fields_count類型,描述的是字段表集合內有多少個字段表。
字段表對應的是程序員在.java文件中的字段,字段表的各種型分別對應修飾詞、引用名稱等等
字段表的閱讀方法和常量的閱讀方法一致。
字段表結構以及字段表中各結構類型詳見【查閱表格】
在字段表集合結束後,接下來的兩個字節是method_count類型,描述的是方法表集合中有多少個方法表。
方法表對應的是程序員在.java文件中編寫的方法,方法表的各種型分別對應修飾詞、引用名稱等等。
方法表結構以及方法表中各結構類型詳見【查閱表格】
方法表集合以後四個字節,描述的是屬性表集合。.class文件有不少屬性,每一個屬性,它的名稱須要從常量池中引用一個CONSTANT_Utf8_info類型的常量表示。
Java程序方法體內的代碼通過javac編譯處理以後,最終編程字節碼指令存儲在Code屬性內。這以後就涉及到了字節碼執行引擎的問題,以後會在其餘的博客中進行講解,敬請期待。
在屬性表集合以後就是Code屬性,具體對應的類型詳見【查閱表格】
對於.class文件的解析工做,jdk爲咱們提供了類解析工具javap。javap生成的.class文件解析比較直觀,容易理解,算是半生肉。結合上文講述的各個概念,應該不難理解。
具體使用方法是在cmd中輸入:
javap -verbose 類名
輸出結果大體是這樣:(以ClassTest.class爲例)
常量池類型表
全部結構類型
訪問標誌
字段表結構
字段表訪問標誌
==各標誌的含義和其後半段的內容一致,表示字段的修飾符==
描述符標誌字符含義
==對於數組類型,每一位都使用一個前置的「[」來描述。好比一個java.lang.String[][] 被記錄爲 [[Ljava/lang/String
一個int[]被記錄爲[I==
方法表結構
方法訪問標誌
Code 屬性
2020年01月14日 17時03分40秒
打賞一下: