Java二進制Class文件格式解析
一、Java Class文件是什么
《The JavaTM Virtual Machine Specification》(Second Edtion)中有表述:Java Class文件由8位字節流組成,所有的16位、32位和64位數據分別通過讀入2個、4個和8個字節來構造,多字節數據總是按照Big-endian順序來存放,即高位字節在前(放在低地址)。每個Class文件都包含且僅包含一個Java類型(類或者接口)。
或許,《The JavaTM Virtual Machine Specification》中的表述不夠明確,那么我們可以參考一下《Inside the Java Virtual Machine》(Second Edtion)中的表述:Java Class文件特指以.class為后綴名的Java虛擬機可裝載的文件。
分析一下兩者的表述,我覺得都不夠全面、不夠明確。我是這么定義的:Java Class文件就是指符合特定格式的字節流組成的二進制文件。這個特定的格式就是指第二節要討論的Class文件格式,亦即在《The JavaTM Virtual Machine Specification》中定義的Class文件格式。從另一個角度來說,這個特定格式就是指JVM能夠識別、能夠裝載的格式。為什么這么說呢?因為JVM在裝載class文件時,要進行class文件驗證,以保證裝載的class文件內容符合正確的內部結構。這個內部結構指的就是這個特定格式,只要是符合這個特定格式的Class文件都是合法的、規范的Class文件,都是JVM能夠裝載的Class文件。如果覺得這樣的表述還是不夠明確,我只能建議你讀完這篇文章之后再回頭來理解看看了J
為了討論方便,在下文中將對這兩個參考資料做個簡記:
1)《The Java Virtual Machine Specification》(Second Edtion)簡記為《JVM Spec》(2nded)。
2)《Inside the Java Virtual Machine》(Second Edtion) 簡記為《Inside JVM》(2nded)。
二、Java Class文件的格式
在講Class文件的格式之前,要介紹三個概念:
1)數據類型:《JVM Spec》(2nded)中指出,Java Class文件的數據用自己定義的一個數據類型集來表示,即u1,u2,u4,分別用于表示一個無符號類型的、占1,2,4個字節的數據。在《Inside JVM》(2nded)一書中,作者把這個數據類型集稱之為Class文件的基本類型,本人覺得比較形象,便于理解。所以,在本文中,我們也用基本類型來表示Java Class文件的數據。
2)表:根據《JVM Spec》(2nded)中的定義,表(table)由項(定義見3)組成,用于幾種Class文件結構中。《JVM Spec》(2nded)中指出,Java Class文件格式用一個類似于C結構的記號編寫的偽結構來表示。這個偽結構指的就是這里的表,例如下面的ClassFile表就是這種偽結構的一個典型例子,下文中所有的表都是指這種偽結構的表。表的大小是可變的,這是因為它的組成部分項是可變的。注意;這里的可變是針對Class層次而言的,即在不同的Class文件中該項的大小可能不一樣的,但是對于每一個具體的Class文件來說,這個項的大小又是一定的,因而這個表的大小也是一定的。那么,項為什么是可變的呢?請看下面的分析。
3)項:描述Java Class文件格式的結構的內容稱為項(items)。每個項都有自己的類型和名稱。項的類型可能是基本類型,也可能是一個表的名字,這種項都是一些數組項。數組項的每一個元素都是一個表,這個表同頂層的ClassFile表一樣,也都是一種偽結構,也都是由一些項構成的,而且這些表不一定是同一種格式的,因此數組項也可以看作一個可變大小的結構流J。這些表對于該數組項來說就是子項,當然子項可能還有子項(目前子項的深度最多就兩層)。項的名稱,沒有什么好說的,就是《JVM Spec》(2nded)中指定的一些名稱。另外,項也是有大小的,對于沒有子項的項來說,其大小是固定的;對于有子項的項來說,其大小是可變的。在一個具體的Class文件中,一個可變項(數組)的大小都會在其前一項中指定,為什么會是這樣的呢?因為《JVM Spec》(2nded)中就是這么定義的!在Class文件中,每個項按規范中定義好的順序存儲在Class文件中,相鄰的項之間沒有任何間隔,連續的項(數組)也是按順序存儲,不進行填充或者對齊,這樣可以使Class文件緊湊。
好了,我想這三個概念我已經解釋地比較清楚了,下面開始正式解析Class文件的格式。
首先要來解析一下ClassFile表結構,這是《JVM Spec》(2nded)中定義的Class文件最外層的結構,換言之,就是Class文件的格式。
ClassFile表結構
ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_Class; u2 super_Class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; } |
ClassFile表結構由16個不同的項組成,其中的各項可以簡要地分析如下:
(1) magic
每個Class文件的前4個字節被稱為它的魔數(magic number): 0xCAFEBABE。魔數的作用在于:可以輕松地分辨出Java Class文件和非Java Class文件。(如果一個文件不是以0xCAFEBABE開頭,它就肯定不是Java Class文件,因為它不符合規范J)。當Java還稱為“Oak”的時候,這個魔數就已經定下來了,它預示了Java這個名字的出現。魔數的來歷請大家自己查閱J
(2) minor_version和major_version
Class文件的下面4個字節包含了次、主版本號。通常只有給定主版本號和一系列次版本號后,Java虛擬機才能夠讀取Class文件。如果Class文件的版本號超出了Java虛擬機所能夠處理的有效范圍,Java虛擬機將不會處理該Class文件。例如J2SE5.0版本的虛擬機就不能執行由J2SE6.0版本的編譯器編譯出來的Class文件。
(3) constant_pool_count
版本號后面的項是constant_pool_count即常量池計數項,該項的值必須大于零,它給出該Class文件中常量池列表項的元素個數,這個計數項包括了索引為0的constant_pool表項,但是該表項不出現在Class文件的constant_pool列表中,因為它被保留為Java虛擬機內部實現使用了,因此常量池列表的元素個數constant_pool_count-1,各個常量池表項的索引值分別為1到constant_pool_count-1。
注:在這里,有幾個術語需要解釋一下,常量池即為constant_pool,常量池列表就是指constant_pool[ ],常量池表項即指常量池列表中的某一個具體的表項(元素)。這些常量池表項的可能類型如下述的cp_type表所示:
cp_type
入口類型 標志值
CONSTANT_Class 7
CONSTANT_Fieldref 9
CONSTANT_Methodref 10
CONSTANT_InterfaceMethodref 11
CONSTANT_String 8
CONSTANT_Integer 3
CONSTANT_Float 4
CONSTANT_Long 5
CONSTANT_Double 6
CONSTANT_NameAndType 12
CONSTANT_Utf8 1
(4) constant_pool[ ]
constant_pool_count項下面是constant_pool[ ]項,即常量池列表,其中存儲了該ClassFile結構及其子結構中引用的各種常量,諸如文字字符串、final變量值、類名和方法名等等。在Java Class文件中,常量池表項是用一個cp_info結構來描述的,常量池列表就是由constant_pool_count-1個連續的、可變長度的cp_info表結構構成的constant_pool[ ]數組。為什么是constant_pool_count-1個constant_pool的原因,在上面已經解釋了。每一個常量池表項都是一個變長結構,其通常格式如下所示:
cp_info
cp_info表的tag項是一個無符號的byte類型值,它表明了cp_info表的類型和格式,具體的tag類型見上表。
需要說明的是,cp_info只是一個抽象的概念,在Class文件中,它表現為一系列具體的、形如CONSTANT_Xxxx_info的constant_pool結構,其具體的格式由cp_info表的tag項(即第一個字節)來確定。不同的cp_info表,其info[]項也是不一樣的,例如,CONSTANT_Class_info表的info[]項為“u2 name_index”,而CONSTANT_Utf8_info表的info[]項為“u2 length; u1 bytes[length];”,顯然,這兩個cp_info表是不一樣的,大小更是不一樣的,因而常量池表項的大小是可變的。由于常量池列表中的每個常量池表項的結構是不一樣,因此常量池列表的大小也是可變的。在Class文件中,常量池列表項是一個可變長度的結構流。
由cp_info表以及cp_type表我們可以知道,若cp_info表中tag(標志)項的值為1時,當前的cp_info就是一個CONSTANT_Utf8_info表結構,若cp_info表中tag項的值為3,當前的cp_info就是一個CONSTANT_Integer_info表結構,其它情況類推。這些表的結構可以查閱《JVM Spec》(2nded)的第四章或者《Inside JVM》(2nded)的第六章。
(5) access_flags
緊接常量池后的兩個字節稱為access_flags,access_flags項描述了該Java類型的一些訪問標志信息。例如,訪問標志指明文件中定義的是類還是接口;訪問標志還定義了在類或接口的聲明中,使用了哪些修飾符;類和接口是抽象的還是公共的等等。實際上,access_flags項的值是Java類型聲明中使用的訪問標志符的掩碼(mask,這里掩碼指的是access_flags的值是所有訪問標志值的總和,當然,未被使用的標志位在Class文件中都被設置為0。例如,若access_flags的值就是0x0001,就表示該Java類型的訪問標志符是ACC_PUBLIC;若access_flags的值是0x0011,就表示該Java類型的訪問標志符是ACC_PUBLIC和ACC_FINAL,因為只有這兩個標志位的和才可能是0x0011;其它情況類推)。
一個Java類型的所有access_flags標志符如下表所示:
access_flags
標志名稱 值 含義
ACC_PUBLIC 0x0001 聲明為public,可以從它的包外訪問
ACC_FINAL 0x0010 聲明為final,不允許有子類
ACC_SUPER 0x0020 用invokespecial指令處理超類的調用
ACC_INTERFACE 0x0200 表明是一個接口,而不是一個類
ACC_ABSTRACT 0x0400 聲明為abstract,不能被實例化
需要說明的是,這是針對一個Java類型的訪問標志符列表,有的標志符只有類可以使用,有的標志符只有接口才可以使用,詳情請查閱《JVM Spec》(2nded)。
ClassFile表結構由16個不同的項組成,其中的各項可以簡要地分析如下:
(1) magic
每個Class文件的前4個字節被稱為它的魔數(magic number): 0xCAFEBABE。魔數的作用在于:可以輕松地分辨出Java Class文件和非Java Class文件。(如果一個文件不是以0xCAFEBABE開頭,它就肯定不是Java Class文件,因為它不符合規范J)。當Java還稱為“Oak”的時候,這個魔數就已經定下來了,它預示了Java這個名字的出現。魔數的來歷請大家自己查閱J
(2) minor_version和major_version
Class文件的下面4個字節包含了次、主版本號。通常只有給定主版本號和一系列次版本號后,Java虛擬機才能夠讀取Class文件。如果Class文件的版本號超出了Java虛擬機所能夠處理的有效范圍,Java虛擬機將不會處理該Class文件。例如J2SE5.0版本的虛擬機就不能執行由J2SE6.0版本的編譯器編譯出來的Class文件。
(3) constant_pool_count
版本號后面的項是constant_pool_count即常量池計數項,該項的值必須大于零,它給出該Class文件中常量池列表項的元素個數,這個計數項包括了索引為0的constant_pool表項,但是該表項不出現在Class文件的constant_pool列表中,因為它被保留為Java虛擬機內部實現使用了,因此常量池列表的元素個數constant_pool_count-1,各個常量池表項的索引值分別為1到constant_pool_count-1。
注:在這里,有幾個術語需要解釋一下,常量池即為constant_pool,常量池列表就是指constant_pool[ ],常量池表項即指常量池列表中的某一個具體的表項(元素)。這些常量池表項的可能類型如下述的cp_type表所示:
cp_type
入口類型 標志值
CONSTANT_Class 7
CONSTANT_Fieldref 9
CONSTANT_Methodref 10
CONSTANT_InterfaceMethodref 11
CONSTANT_String 8
CONSTANT_Integer 3
CONSTANT_Float 4
CONSTANT_Long 5
CONSTANT_Double 6
CONSTANT_NameAndType 12
CONSTANT_Utf8 1
(4) constant_pool[ ]
constant_pool_count項下面是constant_pool[ ]項,即常量池列表,其中存儲了該ClassFile結構及其子結構中引用的各種常量,諸如文字字符串、final變量值、類名和方法名等等。在Java Class文件中,常量池表項是用一個cp_info結構來描述的,常量池列表就是由constant_pool_count-1個連續的、可變長度的cp_info表結構構成的constant_pool[ ]數組。為什么是constant_pool_count-1個constant_pool的原因,在上面已經解釋了。每一個常量池表項都是一個變長結構,其通常格式如下所示:
cp_info
cp_info表的tag項是一個無符號的byte類型值,它表明了cp_info表的類型和格式,具體的tag類型見上表。
需要說明的是,cp_info只是一個抽象的概念,在Class文件中,它表現為一系列具體的、形如CONSTANT_Xxxx_info的constant_pool結構,其具體的格式由cp_info表的tag項(即第一個字節)來確定。不同的cp_info表,其info[]項也是不一樣的,例如,CONSTANT_Class_info表的info[]項為“u2 name_index”,而CONSTANT_Utf8_info表的info[]項為“u2 length; u1 bytes[length];”,顯然,這兩個cp_info表是不一樣的,大小更是不一樣的,因而常量池表項的大小是可變的。由于常量池列表中的每個常量池表項的結構是不一樣,因此常量池列表的大小也是可變的。在Class文件中,常量池列表項是一個可變長度的結構流。
由cp_info表以及cp_type表我們可以知道,若cp_info表中tag(標志)項的值為1時,當前的cp_info就是一個CONSTANT_Utf8_info表結構,若cp_info表中tag項的值為3,當前的cp_info就是一個CONSTANT_Integer_info表結構,其它情況類推。這些表的結構可以查閱《JVM Spec》(2nded)的第四章或者《Inside JVM》(2nded)的第六章。
(5) access_flags
緊接常量池后的兩個字節稱為access_flags,access_flags項描述了該Java類型的一些訪問標志信息。例如,訪問標志指明文件中定義的是類還是接口;訪問標志還定義了在類或接口的聲明中,使用了哪些修飾符;類和接口是抽象的還是公共的等等。實際上,access_flags項的值是Java類型聲明中使用的訪問標志符的掩碼(mask,這里掩碼指的是access_flags的值是所有訪問標志值的總和,當然,未被使用的標志位在Class文件中都被設置為0。例如,若access_flags的值就是0x0001,就表示該Java類型的訪問標志符是ACC_PUBLIC;若access_flags的值是0x0011,就表示該Java類型的訪問標志符是ACC_PUBLIC和ACC_FINAL,因為只有這兩個標志位的和才可能是0x0011;其它情況類推)。
一個Java類型的所有access_flags標志符如下表所示:
access_flags
標志名稱 值 含義
ACC_PUBLIC 0x0001 聲明為public,可以從它的包外訪問
ACC_FINAL 0x0010 聲明為final,不允許有子類
ACC_SUPER 0x0020 用invokespecial指令處理超類的調用
ACC_INTERFACE 0x0200 表明是一個接口,而不是一個類
ACC_ABSTRACT 0x0400 聲明為abstract,不能被實例化
需要說明的是,這是針對一個Java類型的訪問標志符列表,有的標志符只有類可以使用,有的標志符只有接口才可以使用,詳情請查閱《JVM Spec》(2nded)。
posted on 2013-10-22 10:23 順其自然EVO 閱讀(340) 評論(0) 編輯 收藏 所屬分類: android