目錄
前言
最近在研究Java的反射和動態代理,發現使用這兩個Java神器需要了解.class文件的字節碼。於是翻閱了相關資料,在這篇博客中進行一番整理,也作為自己學習的記錄。
如何閱讀class文件
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.class
要想能讀懂class文件這種生肉干貨,首先要理解.class文件中的一些基本概念
基本概念
無符號數&表
無符號數是一種基本的數據類型,通常用u1,u2,u4,u8代表1、2、4、8個字節的無符號數。
無符號數是用來描述數字、索引引用、數量值或者UTF-8編碼的字符串值,可以稱作是.class文件的基本組成單位
表是由多個無符號數或其他表構成的復合數據類型,整個.class文件的本質就是一個表。(表大都習慣性的以_info結尾)
無論是無符號數,還是表,當需要描述同一類型但數量不定的多數據的時候,經常會使用一個位置的容量計數器加若干個連續的數據項的形式。
常量池
魔數(magic number) & 版本號
每個.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類型的常量表示。
Code屬性
Java程序方法體內的代碼經過javac編譯處理之后,最終編程字節碼指令存儲在Code屬性內。這之后就涉及到了字節碼執行引擎的問題,之后會在其他的博客中進行講解,敬請期待。
在屬性表集合之后就是Code屬性,具體對應的類型詳見【查閱表格】
使用javap解析class文件
對於.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秒
打賞一下: