干貨!直擊JVM底層 —— Java Class字節碼文件解析


目錄

前言

最近在研究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

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為例)
結果一

結果二

查閱表格

常量池類型表
常量池類型表

所有結構類型
所有結構類型

訪問標志
訪問標志

字段表結構
字段表結構

字段表訪問標志
access_flags
各標志的含義和其后半段的內容一致,表示字段的修飾符

描述符標志字符含義
描述符標志字符

對於數組類型,每一位都使用一個前置的“[”來描述。比如一個java.lang.String[][] 被記錄為 [[Ljava/lang/String
一個int[]被記錄為[I

方法表結構
方法表結構

方法訪問標志
方法訪問標志

Code 屬性
code屬性

2020年01月14日 17時03分40秒

打賞一下:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM