JVM學習--Class類檔案結構
一個例子(下面的表根據這個例子講解)
public class test{
private int m;
public int inc(){
return m+1;
}
}
經過javac test.java
生成test.class
。使用WinHex檢視如下:
Class類檔案結構
Class檔案格式
u1~8代表 1 ~ 8個位元組的無符號數,_info代表表
型別 | 名稱 | 數量 |
---|---|---|
u4 | 魔術 | 1 |
u2 | 次版本號 | 1 |
u2 | 主版本號 | 1 |
u2 | 常量池數量 | 1 |
cp_info | constant_pool | 常量池數量 - 1 |
u2 | 訪問標識 | 1 |
u2 | 本類標識 | 1 |
u2 | 父類標識 | 1 |
u2 | 介面數量 | 1 |
u2 | 介面 | 介面數量 |
u2 | 欄位數量 | 1 |
field_info | 欄位 | 欄位數量 |
u2 | 方法數量 | 1 |
method_info | 方法 | 方法數量 |
u2 | 屬性數量 | 1 |
attribute_info | 屬性 | 屬性數量 |
魔術
標識這個檔案是java檔案,固定為:
CA FE BA BE
cafe babe記為咖啡寶貝。
次版本號
現在好像基本都是
00 00
主版本號
00 34
34(16進位制) = 52(10進位制)
版本和版本號對應如下:
Java 1.2 uses major version 46
Java 1.3 uses major version 47
Java 1.4 uses major version 48
Java 5 uses major version 49
Java 6 uses major version 50
Java 7 uses major version 51
Java 8 uses major version 52
Java 9 uses major version 53
Java 10 uses major version 54
Java 11 uses major version 55
常量池計數
需要注意的是計數是從1開始的。0代表的是某些指向常量池索引值的資料在特定情況下需要表達“不引用任何一個常量池專案”的意思。
00 13
0x0013 -> 19 代表常量池中有19-1=18項常量,索引為1~18.
常量池
常量池中存放兩大類常量:字面量(literal)和符合引用(Symbolic References)。
字面量接近於java語言層面的常量概念,如String,被宣告為final的常量值等;
符合引用主要包含以下三種常量:
- 類和介面的全限定名
- 欄位的名稱和描述符
- 方法的名稱和描述符
當虛擬機器執行時,需要從常量池獲取對應的符號引用,再在類建立時或執行時解析並翻譯到具體的記憶體地址中。
這些表都有一個共同的特點,就是表的第一位是一個u1型別的標誌位。
Constant Type | Value | 描述 |
---|---|---|
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 | utf-8編碼的字串 |
CONSTANT_MethodHandle | 15 | 方法處理控制程式碼 |
CONSTANT_MethodType | 16 | 方法型別 |
CONSTANT_InvokeDynamic | 18 | 動態方法的呼叫點 |
由於每一項常量表都有自己的結構,這裡就不展開講。就講第一個
0A
0A -> 10 對照上面的表發現是類中方法的符號引用。去查對應的結構為:
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
u1一個位元組的tag已經讀取了,接下來是u2兩個位元組的類索引
00 04
這個index代表指向常量池第4項,這裡不能直接得到第4項還要接著往下看。
接下來是u2兩個位元組的欄位或方法的部分符號的索引
00 0F
這個index指向常量池第15項,目前也無法得到o(╥﹏╥)o
這裡刷下花招,用javap看看。發現上面的解析並沒有錯誤。
訪問標誌
訪問標誌用於識別一些類或介面層次的訪問資訊,包括:這個Class是類還是介面;是否定義為public等。
Flag Name | Value | 描述 |
---|---|---|
ACC_PUBLIC | 0x0001 | 標識是public型別 |
ACC_FINAL | 0x0010 | 標識是final型別 |
ACC_SUPER | 0x0020 | 標識允許使用invokespecial位元組碼指令,JDK1.2後預設為true |
ACC_INTERFACE | 0x0200 | 標識這是一個介面 |
ACC_ABSTRACT | 0x0400 | 標識抽象型別 |
ACC_SYNTHETIC | 0x1000 | 標識這個類並非由使用者程式碼生成 |
ACC_ANNOTATION | 0x2000 | 標識這是一個註解 |
ACC_ENUM | 0x4000 | 標識這是一個列舉 |
access_flags中共有16個標誌位可以使用,當前只定義了其中8個,沒有使用到的標誌位一律為0。
使用到的標識value進行 或
運算,例如test類只用到了public
修飾,所以ACC_PUBLIC 和 ACC_SUPER為真。所以值為0x0001 | 0x0020 = 0x0021
。
類索引、父類索引、介面索引集合
類索引和父類索引都是一個u2型別的資料,介面集合(包含介面數量+介面)是一組u2型別的資料的集合,Class由這3項資料確定類的繼承關係。
類索引是類的全限定名。
父類索引是類的父類也就是extends。
介面數量+介面表明介面的個數和名稱就是implements(如果這個類是介面則是extends)。
索引指向的是常量池裡的CONSTANT_Class_info,這個索引最終會指向一個CONSTANT_Uft8_info型別的全限定名字串。
除了java.lang.Object其他所有的父索引都不為0.
如果介面數量為0,後面的介面不再佔用任何位元組。
欄位表集合
欄位表用於描述介面或類中宣告的變數。欄位包含了類級變數和例項級變數,但不包括在方法內宣告的變數。
欄位表結構:
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
其中 access_flags 包含:這個很好理解,就不解釋了。
Flag Name | Value | Interpretation |
---|---|---|
ACC_PUBLIC | 0x0001 | Declared public; may be accessed from outside its package. |
ACC_PRIVATE | 0x0002 | Declared private; usable only within the defining class. |
ACC_PROTECTED | 0x0004 | Declared protected; may be accessed within subclasses. |
ACC_STATIC | 0x0008 | Declared static. |
ACC_FINAL | 0x0010 | Declared final; never directly assigned to after object construction (JLS §17.5). |
ACC_VOLATILE | 0x0040 | Declared volatile; cannot be cached. |
ACC_TRANSIENT | 0x0080 | Declared transient; not written or read by a persistent object manager. |
ACC_SYNTHETIC | 0x1000 | Declared synthetic; not present in the source code. |
ACC_ENUM | 0x4000 | Declared as an element of an enum. |
name_index和descriptor_index 都是對常量池的引用,分別代表著欄位的簡單名稱及欄位和方法的描述符。簡單名稱指的是 沒有任何修飾符的方法名或欄位名,這個類中就是inc和m。
全限定名是把.
換成/
,且在最後加;
例如java.lang.String --> java/lang/String;
描述符用來描述欄位的資料型別、方法的引數列表和返回值。根據描述符規則,基本資料型別及代表無返回值的void型別都用一個大寫字元V來表示,而物件型別則用字元L加物件的全限定名來表示。
FieldDescriptor:
FieldType
FieldType:
BaseType
ObjectType
ArrayType
BaseType:
B byte
C char
D double
F float
I int
J long(注意區別)
S String
Z boolean
V void返回值
ObjectType:
L ClassName ; 例如 `Ljava/lang/Object;`
ArrayType:
[ ComponentType 保留一個前置的括號例如:int[][] --> [[I
ComponentType:
FieldType
描述方法按照先引數列表,後返回值的順序描述,引數列表按照引數的嚴格順序放在一組小括號裡。
例如:void inc() --> ()V
再例如java.lang.String.copyValueOf(char data[], int offset, int count) ---> ([CII)Ljava/lang/String;
選定的淺藍色就是這段內容,解讀如下:1個欄位,修飾符是2(private),欄位名是5(m),描述是6(I).連起來就是private int m
descriptor_index;後還可能有屬性表,用來描述額外的資訊。如果將欄位m的宣告改為"final static int m = 123",那就可能存在一項名為ConstantValue的屬性,值指向常量123.
欄位表不會列出從父類或介面中繼承來的欄位,但有可能列出原來java程式碼中不存在的欄位。
方法表集合
方法表結構和欄位幾乎一樣。
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
方法訪問標誌有所差異
Flag Name | Value | Interpretation |
---|---|---|
ACC_PUBLIC | 0x0001 | Declared public; may be accessed from outside its package. |
ACC_PRIVATE | 0x0002 | Declared private; accessible only within the defining class. |
ACC_PROTECTED | 0x0004 | Declared protected; may be accessed within subclasses. |
ACC_STATIC | 0x0008 | Declared static. |
ACC_FINAL | 0x0010 | Declared final; must not be overridden (§5.4.5). |
ACC_SYNCHRONIZED | 0x0020 | Declared synchronized; invocation is wrapped by a monitor use. |
ACC_BRIDGE | 0x0040 | 是否是編譯器產生的橋接方法 |
ACC_VARARGS | 0x0080 | 是否接受可變引數 |
ACC_NATIVE | 0x0100 | Declared native; implemented in a language other than Java. |
ACC_ABSTRACT | 0x0400 | Declared abstract; no implementation is provided. |
ACC_STRICT | 0x0800 | Declared strictfp; floating-point mode is FP-strict. |
ACC_SYNTHETIC | 0x1000 | Declared synthetic; not present in the source code. |
方法經過編譯後,方法內的程式碼存放在一個名為Code的屬性裡面,屬性表是Class檔案格式最具有擴充套件性的一種資料專案。
00 02 代表兩個方法,一個方法是編譯器新增的例項構造器,另一個是原始碼中的inc()。第一個方法的訪問標識值為00 01就是public,方法名為00 07:描述索引值為00 08對應常量為()V
,屬性表計數器為00 01代表有一個屬性 再檢視00 09發現為Code,說明該屬性是方法的位元組碼描述。
如果父類方法在子類中沒有被重寫,方法表中就不會出現父類的方法資訊。但是有可能出現編譯器自動新增的方法,最典型的是類構造器和方法。
屬性表集合
屬性表限制比較寬鬆,而且可以自定義,篇幅超級無敵長,這裡就隨便拿個。。Code屬性的
Code_attribute {
u2 attribute_name_index; 屬性名稱
u4 attribute_length; 屬性值的長度(整個屬性表 - 6),6是attribute_name_index+ attribute_length的長度
u2 max_stack; 運算元棧深度的最大值
u2 max_locals;區域性變數表所需的儲存空間
u4 code_length; 位元組碼長度
u1 code[code_length]; 位元組碼指令的位元組流
u2 exception_table_length;
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
}
exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
如果位元組碼從第start_pc行到第end_pc行之間出現了型別為 catch_type或其子類的異常,則轉到第handler_pc行繼續處理。當catch_type的值為0時,代表任何的異常情況都需要轉向到 handler_pc進行處理。
參考
周志明著《深入理解java虛擬機器》
https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html
相關文章
- JVM學習筆記——Class類檔案解讀JVM筆記
- 深入理解JVM(五)Class類的檔案結構JVM
- Class類檔案結構
- 類檔案結構_class類檔案的的結構
- JVM(筆記)—— Class 類檔案結構的說明(二)JVM筆記
- JAVA Class類檔案結構Java
- 【JVM】深入解析class類檔案JVM
- 深入解析Class類檔案的結構
- JVM 深入學習:Java 解析 Class 檔案過程解析JVMJava
- Java虛擬機器之Class類檔案結構Java虛擬機
- 《深入理解java虛擬機器》學習筆記5——Java Class類檔案結構Java虛擬機筆記
- JVM虛擬機器Class類檔案研究分析JVM虛擬機
- 【JVM】JVM系列之Class檔案(三)JVM
- 深入理解jvm-2Edition-類檔案結構JVM
- 要點提煉| 理解JVM之類檔案結構JVM
- 深入學習Java虛擬機器——類檔案結構Java虛擬機
- Jvm之用java解析class檔案JVMJava
- 【深入Java虛擬機器】之二:Class類檔案結構Java虛擬機
- Class檔案結構&位元組碼指令
- ☕[Java技術指南](1)Class類檔案的結構介紹(上篇)Java
- 例項分析JAVA CLASS的檔案結構Java
- Java Class檔案結構例項分析(下)Java
- Java Class檔案結構例項分析(上)Java
- JVM學習(一)——記憶體結構JVM記憶體
- Java Class 位元組碼檔案結構詳解Java
- JVM載入Class檔案的原理機制JVM
- 檔案同步類SimFileSync.class.phpPHP
- JVM學習總結JVM
- PE檔案結構複習
- class檔案的基本結構及proxy原始碼分析二原始碼
- [深入理解Java虛擬機器]第六章 Class類檔案的結構Java虛擬機
- 類檔案的結構、JVM 的類載入過程、類載入機制、類載入器、雙親委派模型JVM模型
- 深入理解JVM類檔案格式JVM
- TypeScript學習筆記之五類(Class)TypeScript筆記
- Java虛擬機器——類檔案結構Java虛擬機
- 物聯網學習教程—Linux 可執行檔案結構與程式結構Linux
- 【LINUX學習】連結檔案Linux
- JVM學習(三)——類載入機制JVM