在Java虛擬機中,類和對象是程式的基本組成單元。類定義了一組對象的共性特征和行為,是Java程式中最基本的代碼單元。而對象則是具體的實例,有自己獨特的狀態和行為。在JVM中,類和對象都需要進行存儲,因此瞭解類和對象的存儲基礎知識對於Java程式員來說是非常重要的。 ...
1 類文件數據結構類型
Class文件結構主要有兩種數據結構:無符號數和表
•無符號數:用來表述數字,索引引用、數量值以及字元串等,比如 圖1中類型為u1,u2,u4,u8分別代表1個位元組,2個位元組,4個位元組,8個位元組的無符號數
•表:表是有由多個無符號數以及其它的表組成的複合結構,比如圖1中類型以_info結尾的項為表類型。
2 類結構定義
Class類文件是緊湊、順序、無空隙的,魔數(MagicNumber)、Class文件版本(Version)、常量池(Constant_Pool)、訪問標記(Access_flag)、本類(This_class)、父類(Super_class)、介面(Interfaces)、欄位集合(Fields)、方法集合(Methods )、屬性集合(Attributes)。其中因為java多繼承所以interfaces介面類型為數組;attribute_info則是方法表中定義的code索引,指向具體的方法體位元組碼。如圖1所示。
下麵用一段程式做說明,此類有介面,有方法、類變數和實例變數,機器是如何識別位元組碼然後按照上面的規則來定義此class類呢?
package com.jd.crm.Logback;
public class TestClass implements Super{
private static final int staticVar = 0;
private int instanceVar=0;
public int instanceMethod(int param) throws Exception{
return param ++;
}
}
interface Super{ }
通過javap幫助解析class文件格式如下:
Classfile /D:/spm-workspace/test/target/classes/com/jd/crm/Logback/TestClass.class
Last modified 2023-4-14; size 597 bytes
MD5 checksum 9d5dd9fc2145ac17393fee7a707d3b9c
Compiled from "TestClass.java"
public class com.jd.crm.Logback.TestClass implements com.jd.crm.Logback.Super
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#26 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#27 // com/jd/crm/Logback/TestClass.instanceVar:I
#3 = Class #28 // com/jd/crm/Logback/TestClass
#4 = Class #29 // java/lang/Object
#5 = Class #30 // com/jd/crm/Logback/Super
#6 = Utf8 staticVar
#7 = Utf8 I
#8 = Utf8 ConstantValue
#9 = Integer 0
#10 = Utf8 instanceVar
#11 = Utf8 <init>
#12 = Utf8 ()V
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 LocalVariableTable
#16 = Utf8 this
#17 = Utf8 Lcom/jd/crm/Logback/TestClass;
#18 = Utf8 instanceMethod
#19 = Utf8 (I)I
#20 = Utf8 param
#21 = Utf8 Exceptions
#22 = Class #31 // java/lang/Exception
#23 = Utf8 MethodParameters
#24 = Utf8 SourceFile
#25 = Utf8 TestClass.java
#26 = NameAndType #11:#12 // "<init>":()V
#27 = NameAndType #10:#7 // instanceVar:I
#28 = Utf8 com/jd/crm/Logback/TestClass
#29 = Utf8 java/lang/Object
#30 = Utf8 com/jd/crm/Logback/Super
#31 = Utf8 java/lang/Exception
{
public com.jd.crm.Logback.TestClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_0
6: putfield #2 // Field instanceVar:I
9: return
LineNumberTable:
line 3: 0
line 7: 4
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/jd/crm/Logback/TestClass;
public int instanceMethod(int) throws java.lang.Exception;
descriptor: (I)I
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=2
0: iload_1
1: iinc 1, 1
4: ireturn
LineNumberTable:
line 10: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/jd/crm/Logback/TestClass;
0 5 1 param I
Exceptions:
throws java.lang.Exception
MethodParameters:
Name Flags
param
}
SourceFile: "TestClass.java"
以上是javap幫助我們生成的class文件解析結果,只是給人看,而非機器。
通過編譯後生成class文件格式如下,因為class文件是以8位作為一個位元組的二進位流。為了方便計算,用16進位表示二進位(1個位元組=2個十六進位的數,故下麵每2個數就代表1個位元組)
2.1 魔法數
前四個位元組cafebabe是固定值,任何語言編譯成jvm認識的二進位流,前四位必須是固定的cafebabe位元組。
2.2 版本號
緊接著2個位元組00表示次版本號為0 ;0034代表主版本為52(jdk版本號對應的jdk版本為1.8)參考jdk版本和class位元組版本的對應關係
2.3 常量個數
常量個數const_pool_count位元組碼為00 20對應的說明常量個數為32,實際為31個,因為首位jvm作為保留位使用。
2.4 常量池
常量池存放兩大常量:字面量和符號引,字面量如文本字元串,被生命的final常量值等,而符號引用則包含類、介面的全限名稱、欄位、方法名稱和描述符號等等。參考javap生成的類文件信息。
這裡只分析下其中一個常量,在上面常量個數2個位元組後面緊接著一個位元組0a十進位為10,參考常量池類型10代表類中方法的符號引用。繼續參考方法類型MethodRef_info個格式定義:前兩個位元組0004代表方法所在類名稱的索引,後兩個位元組0001a代表一個NameAndType類型的索引。
2.5 類訪問標誌
緊接常量池定義完後的u2標識訪問標誌,本例標識為0x0021和下圖標誌位按位或計算,如0x0001為真,0x0020也為真,其他為否 最終確認訪問標誌位ACC_PUBLIC、ACC_SUPER
2.6 本類、父類、介面索引集合
根據圖1的規則,u2兩個位元組0003標識當前類名的引用到,引用常量池數組下標為#3,根據圖3所示子項的類名為com/jd/crm/Logback/TestClass;0004代表父類類名的引用常量池數組下標為#4,根據圖4所示引用的父類類名為java/lang/Object;緊接著0001標識介面個數,指明數量為1,0005標識第一個介面數組中介面的名稱,指向常量池中下標為5的名稱為com/jd/crm/Logback/Super;
比如查找當前類索引如下圖
2.7 欄位表集合
欄位表以數組的形式定義存儲在常量表中
以上圖說明,0002標識域個數為2個域標識,在本類中有兩個,一個類的域欄位staticVar 一個是實例對象的域欄位instanceVar,如欄位結構定義(下圖)定義,前2個位元組001a為訪問標識,和類訪問標識一樣,分別用001a的二進位和下圖欄位域訪問標識類型做位或運算,得出訪問類型為ACC_PRIVATE類型。name_index的占用兩個位元組0006,指向常量表下標為6的引用,descriptor_index=0007指向常量表下標為7的引用,此處為I標識為數據類型為int,attributes_count=0001為1個,值為0008指向常量表下標為#8的引用常量ConstantValue,標識為靜態變數,最終依次類推第二個域標識引用
欄位結構定義
欄位域的訪問標誌請參考類訪問標誌,邏輯計算一致,只是規則不一樣而已 如下圖
2.8 方法表集合
和域欄位集合表定義類似 也是數組方式定義在常量池中 ,其中方法的結構體第四個欄位attributes_count代表方法的屬性數量,attribute_info就是屬性的集合參考屬性表集合
方法表訪問標識類型
通過上面方法的訪問標誌、名稱索引和描述索引定義方法的基本信息,方法的代碼塊則存放於類型為Code的屬性表中。
2.9 屬性表集合
類、欄位表、方法表本身可包含屬性表,屬性表格結構體如下,屬性表結構類型較多,比如有Code類型、Exception類型、MethodParameters類型等等,具體參考屬性表類型。所有的屬性都是引用常量池中的屬性類型名稱。然後根據屬性的長度指定該屬性的內容,根據屬性的不同類型解析不同的屬性值。格式定義如下
以Code屬性舉例,Code屬性結構如下所示
jvm按屬性獲取attribute_name_index指向常量池一個字元串常量Code,緊接著attribute_length標識Code類型Info信息長度,這個info內容包括:max_stack 最大棧深,max_locals局部變數槽數量,code_length標識機器位元組碼長度,往後查詢位元組碼如下圖所示,其實就是0/1/4/5/6/9的指令集。Code類型又嵌套異常屬性表、行號表LineNumberTable、LocaVariableTable 局部變數表等等信息。如下圖javap生成的類定義信息
1.Code1方法執行過程:
構造方法:descriptor ()V標識無參無返回值為Void的方法索引,flags可見性修飾符;
程式運行時,先將常量池、方法位元組碼、字元串常量池,靜態變數載入到元數據區(1.8後字元串常量池,靜態變數放入了堆);main線程開始運行,分配棧幀記憶體,其中操作數棧stack=2表示運行該方法所需要的最大操作數棧的深度是2;locals=1表示該運行方法所需要的最大局部方法表的最大slot數據是1;args_size是該方法的形參個數,如果是實例方法 第一個形參是this引用。此例正是this引用。所以args_size=1+實際的參數
aload_0: 載入 slot0的局部變數,即this,作為下麵的invokespecial 構造方法調用的參數
invokespecial: 調用構造方法,常量池第#1項,即【Method java/lang/Object."