JVM學習筆記(二)——Class文件結構

Class文件是Java程序跨平臺的保證,正是由于有了Class文件架起源碼和機器碼之間的中間橋梁,JVM虛擬機才可以在各種平臺上按照統一的規范標準加載Java代碼。

作為“寫給虛擬機看的”Java代碼,Class文件結構必須設計得足夠完善,同時由于Java虛擬機規范并不只針對Java,Class文件又不能引入過多細節。本篇博客我們就來介紹下Class文件的結構。

一個Class文件對應一個Java Class,所以一個Class文件記錄著一個類的全部信息,JVM通過Class文件將對應的類加載入內存。

Class文件的結構主要分為以下幾部分:

  • 魔數
  • 常量池
  • 訪問標識
  • 類索引、父類索引、接口索引
  • 字段表集合
  • 方法表集合
  • 索引表集合

1 魔數

每個Class文件的頭4個字節成為魔數(Magic Number),它的唯一作用就是確定這個文件是否能作為一個Class文件被接受。很多文件都以魔數進行類型識別,如gif、jpeg等圖片文件。之所以使用魔數而不是擴展名是處于安全考慮,文件擴展名可以所以改動。Class文件的魔數是0xCAFEBABE。

緊接著魔數的4個字節存儲的是Class文件的版本號,5、6字節為次版本號,7、8字節為主版本號。不同版本的虛擬機可以接受不同版本的class文件,所以虛擬機通過主次版本號判斷是否可以加載目標class文件。

2 常量池

常量池可以看做是Class文件的資源倉庫,也是Class文件中占用空間最大的部分。常量池主要存放兩大類常量:字面量、符號引用。

字面量比較接近Java語言層面的常量,如文本字符串、生命為final的常量等。

符號引用屬于編譯范疇中的概念,主要包括三類常量:

  • 類和接口的全限定名
  • 字段的名稱和描述符
  • 方法的名稱和描述符

Java語言不同于C、C++等語言在編譯階段即進行鏈接,相應的鏈接都放到了運行時階段。所以Class文件中不可能包含各個方法、字段在內存中的布局。Java虛擬機在運行階段加載類時,將符號引用轉換成真正的內存入口地址,對應類才算可以工作。

常量池中的每一項代表一個常量,JDK目前共有14中類型的常量,而每一個常量又有自己的內部結構。類或接口符號索引是其中較為簡單的一項,接下來以類索引為例做簡單介紹。類符號索引對應的類型為CONSTANT_Class_info,其結構如下:

類型 名稱 數量
u1 tag 1
u2 name_index 1

tag是標志位,表明類型。CONSTANT_Class_info的tag為7。name_index是一個索引值,它指向常量池中一個CONSTANT_Utf8_info類型常量,此常量代表了這個類的全限定名。

CONSTANT_Utf8_info的結構如下所示:

類型 名稱 數量
u1 tag 1
u2 length 1
u1 bytes length

bytes字段的內容就是類的全限定名。

3 訪問標志

常量池之后的兩個字節代表訪問標志(accss_flags),用于識別類或接口的層次訪問信息:

標志名稱 標志值 含義
ACC_PUBLIC 0x0001 是否為public
ACC_FINAL 0x0010 是否被聲明為final
ACC_SUPER 0x0020 是否允許使用invokespecial字節碼指令的新語義
ACC_INTERFACE 0x0200 是否為接口
ACCS_ABSTRACT 0x0400 是否為abstract類型
ACC_SYNTHETIC 0x1000 標示該類并非由用戶代碼產生
ACC_ANNOTATION 0x2000 標示這是一個注解
ACC_ENUM 0x4000 標示這是一個枚舉

4 類索引、父類索引與接口索引集合

類索引(this_class)和父類索引(super_class)都是一個u2類型的數據,而接口索引集合(interfaces)是一組u2類型的數據集合。Class文件中的這三項決定了類的繼承關系。

類索引和父類索引用兩個u2類型的索引值表示,它們各自指向一個CONSTANT_Class_info類描述符常量,通過CONSTANT_Class_info類型的常量索引值可以找到定義在CONSTAN_Utf8_info類型的常量中的類全限定名。

對于接口索引集合,入口的第一項u2類型的數據為接口計數器(interfaces_count)表示索引表的容量。每個接口的同樣由一個u2類型數據指向一個CONSTANT_Class_info。

5 字段表集合

字段表(field_info)用于描述接口或者類中聲明的變量。字段(field)包括類級變量和實例級變量,但不包括定義在方法內部的局部變量。每個字段的結構如下圖所示:

類型 名稱 數量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attribute_count

5.1 訪問標識

標志名稱 含義
ACC_PUBLIC 是否為public
ACC_PRIVATE 是否為private
ACC_PROTECTED 是否為protected
ACC_STATIC 是否為static
ACC_FINAL 是否為final
ACC_VOLATILE 是否為volatile
ACC_TRANSIENT 是否為transient
ACC_SYNTHETIC 是否為編譯器自動產生
ACC_ENUM 是否為enum

字段的訪問標識access_flags與類訪問標識類似。

5.2 name_index

name_index標識字段的簡單名稱。簡單名稱和全限定名的區別在于:全限定名是類的全路徑名,如org/fenixsoft/clazz/TestClass,只是把類全名中的"."替換成“/”而已。簡單名稱指的是沒有類型和參數修飾的方法或者字段名稱,如一個類中含有一個字段"m",則其簡單名稱為"m"。

5.3 descriptor_index

descriptor_index為字段或方法的描述符。描述符的作用是用來描述字段的數據類型、方法的參數列表(包括數量、類型以及順序)和返回值。基本數據類型以及代表無返回值的void以及對象類型均由一個大寫字符來代替:

標志字段 含義
B byte
C char
D double
F float
I int
J long
S short
Z boolean
V void
L 對象類型,如Ljava/lang/object

對于數組類型,每一個維度用一個"["來描述,比如定義一個“java.lang.String[][]”類型的二維數組,將被記錄為“[[Ljava/lang/string”。

方法描述符按照先參數列表后返回值的順序描述,參數列表按照參數順序放在一組"()"之內。如方法int indexOf(char[] source, int sourceOffest, int sourceCount, char[] target, int targetOffest, int targetCount, int fromIndex)的描述符為"([CII[CIII)I"。

5.4 attributes_count attribute_info

在描述符之后還有數量為attributes_count的attribute_info,attribute_info描述字段的額外信息,但這些額外信息最終存放在屬性表中。如“final static int m = 123;”,那就可能會存在一項名稱為ConstantValue的屬性,其值指向常量123。

6 方法表集合

類型 名稱 數量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attribute_count

方法表和字段表結合幾乎一樣,理解了字段表,方法表就非常簡單了。

類型 名稱 數量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attribute_count

由于volatile和transient不能修飾方法,所以方法表的訪問標識中沒有了ACC_VOLATILE,ACC_TRANSIENT標識。但同時又增加了代表synchronized native strictfp abstract的ACC_SYNCHRONIZED ACC_NATIVE ACC_STRICTFP ACC_ABSTRACT。

需要說明的是,方法表集合中并不包含方法里面的代碼。方法代碼經過編譯后存放在方法屬性集合中的一個名為"Code"的屬性里面。例如某方法的屬性表計數器attributes_count為1,則表示方法的屬性表集合有一項屬性,屬性索引名稱為0x0009,對應常量為code,說明此屬性是方法的字節碼描述。

7 屬性表集合

Class文件、字段表、方法表都可以有自己的屬性表,Java7里面定義了21種屬性。

Code屬性

并非所有方法表都有Code屬性,比如接口和抽象類的方法就沒有。結構如下:

類型 名稱 數量 含義
u2 attribute_name_index 1 屬性名的索引,對Code屬性而言恒為”Code”
u4 attribute_length 1 屬性值長度,相當于整個屬性表長度長度減6(u2+u4)
u2 max_stack 1 操作數棧深度最大值。JVM運行時根據此值分配棧楨的操作棧深度
u2 max_locals 1 局部變量表所需存儲空間,單位是Slot,double和long占用2個Slot、其他基本類型1Slot,Slot空間可以重用(變量作用域問題)
u4 code_length 1 編譯后的字節碼長度,理論上最長2^32-1,實際上JVM規定一個方法不允許超過65535條字節碼指令
u1 code code_length 代碼編譯后的字節碼
u2 exception_table_length 1 異常表長度
exception_info exception_table exception_table_length 異常表,記錄字節碼在start_pc到end_pc行之間如果出現類型為catch_type或其子類的異常則跳轉到handler_pc行繼續處理
u2 attibutes_count 1 屬性表計數器
attribute_info attributes attibutes_count 屬性額外描述,比如描述變量初始化值在常量池中的索引

字節碼值得注意的一個地方是,javac編譯時將this關鍵字作為一個普通方法參數由JVM調用時自動傳入。

Exceptions屬性

描述方法可能拋出的受檢異常。

LineNumberTable屬性

描述Java遠嗎行號與字節碼行號之間映射關系,也就是為什么拋異常的時候可以顯示源碼哪一行拋出的。

LocalVariableTable屬性

描述棧楨中局部變量表與Java源碼中變量的關系,以保證編譯后的代碼被其他代碼調用時,IDE可以顯示參數名(否則被arg0、arg1之類的變量名代替)

SourceFile屬性

描述生成當前Class文件的源文件名稱,也是拋異常時可以顯示源文件名字的原因。但內部類不會生成這個屬性。

ConstantValue屬性

static關鍵字修飾的變量可以使用這個屬性。對于Sun javac編譯器,final static的變量采用ConstantValue屬性初始化,其他static變量在<clinit>(類構造器)中初始化。

InnerClasses屬性

記錄內部類和宿主類的關聯。內部類和宿主類的Class文件都會有這個屬性。

Signature屬性

記錄泛型簽名信息。Java的泛型是使用擦除式實現的偽泛型,編譯后擦除泛型,這個屬性為了彌補此缺陷,方便反射API可以拿到泛型類型。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容

  • 字節碼查看工具:WinHex 前言 Java虛擬機實現語言無關性的基石就是Class文件Java虛擬機提供的語言無...
    zlcook閱讀 7,150評論 4 18
  • 晚秋只道意綿綿 枯葉點點 夢里回轉 他鄉更比凄零雨 卻孰知 茶香徐徐映星辰
    蘭閣雨湘閱讀 189評論 4 4
  • “人們數不清她的屋頂上有多少輪皎潔的明月, 也數不清她的墻壁之后那一千個燦爛的太陽?!? ...
    心有靈汐詞為夢閱讀 446評論 0 4
  • Node.js 安裝配置本章節我們將向大家介紹在windows上安裝Node.js的方法。本安裝教程以Node.j...
    pwld閱讀 175評論 2 3