帶你走進JVM之Class類文件

Class類文件結構

我們都知道java程序經過javac xxx.java編譯後會生成xxx.class文件,接下來就要解析這個class類文件。

首先它的結構是什麼呢?
1.Class文件是一組以8個字節爲基礎單位的二進制流(可能是磁盤文件,也可能是類加載器直接生成的),各個數據項目嚴格按照順序- 緊湊地排列,中間沒有任何分隔符。
2.Class文件格式採用一種類似於C語言結構體的僞結構來存儲數據,只有兩種數據類型:無符號數和表。
3.無符號數屬於基本的數據類型,以u1、u2、u4和u8來分別代表1個字節、2個字節、4個字節和8個字節的無符號數,可以用來描述數字- 、索引引用、數量值或者按照UTF-8編碼構成字符串值。
4.表是由多個無符號數獲取其他表作爲數據項構成的複合數據類型,習慣以“_info”結尾。
5.無論是無符號數還是表,當需要描述同一個類型但數量不定的多個數據時,經常會使用一個前置的容量計數器加若干個連續的數據項- 的形式,這時稱這一系列連續的某一類型的數據未某一類型的集合。

.class文件結構表:
在這裏插入圖片描述
魔數:
在這裏插入圖片描述
CAFE BABE -> 固定值如果不是這個虛擬機會拒絕執行。

次版本號:
在這裏插入圖片描述
00 00 -> 0x00 -> 10進制結果就是0。

主版本號:
在這裏插入圖片描述
00 34 -> 0x34 -> 10進制結果就是52。
在這裏插入圖片描述
java主版本就是1.8。

常量池大小:
在這裏插入圖片描述

00 13 -> 0x13 -> 10進制結果就是19 但是需要減去1。得到常量池大小18個。
可通過命令javap -verbose Demo.class查看得到結果:

Constant pool:
    #1 = Methodref          #4.#15         // java/lang/Object."<init>":()V
    #2 = Fieldref           #3.#16         // com/neo/asmtest/Bytecode/Demo.age:I
    #3 = Class              #17            // com/neo/asmtest/Bytecode/Demo
    #4 = Class              #18            // java/lang/Object
    #5 = Utf8               age
    #6 = Utf8               I
    #7 = Utf8               <init>
    #8 = Utf8               ()V
    #9 = Utf8               Code
   #10 = Utf8               LineNumberTable
   #11 = Utf8               getAge
   #12 = Utf8               ()I
   #13 = Utf8               SourceFile
   #14 = Utf8               Demo.java
   #15 = NameAndType        #7:#8          // "<init>":()V
   #16 = NameAndType        #5:#6          // age:I
   #17 = Utf8               com/neo/asmtest/Bytecode/Demo
   #18 = Utf8               java/lang/Object

常量表:
在這裏插入圖片描述
具體結構圖:
在這裏插入圖片描述
描述符對照表:
在這裏插入圖片描述
常量池:
緊接着主版本號的就是常量池,常量池可以理解爲class文件的資源倉庫,它是class文件結構中與其它項目關聯最多的數據類型,也是佔用class文件空間最大的數據項目之一,也是class文件中第一個出現的表類型數據項目。

由於常量池中常量的數量不是固定的,所以常量池入口需要放置一項u2類型的數據,代表常量池中的容量計數。不過,這裏需要注意的是,這個容器計數是從1開始的而不是從0開始,也就是說,常量池中常量的個數是這個容器計數-1。將0空出來的目的是滿足後面某些指向常量池的索引值的數據在特定情況下需要表達“不引用任何一個常量池項目”的含義。class文件中只有常量池的容量計數是從1開始的,對於其它集合類型,比如接口索引集合、字段表集合、方法表集合等的容量計數都是從0開始的。

常量池中主要存放兩大類常量:字面量和符號引用。字面量比較接近Java語言的常量概念,如文本字符串、聲明爲final的常量等。而符號引用則屬於編譯原理方面的概念,它包括三方面的內容:
1.類和接口的全限定名(Fully Qualified Name);
2.字段的名稱和描述符(Descriptor);
3.方法的名稱和描述符;

常量池中的每一項都是一個表,在JDK1.7之前有11中結構不同的表結構,在JDK1.7中爲了更好的支持動態語言調用,又增加了3種(CONSTANT_MethodHandle_info、CONSTANT_MethodType_info和CONSTANT_InvokeDynamic_info)。

訪問標識
在這裏插入圖片描述
00 21 -> 標示表示類或者接口的訪問信息。
如下:
在這裏插入圖片描述
00 21 -> 0x01 + 0x20 -> ACC_SUPER + ACC_PUBLIC
可通過取與操作,判斷這個標識符是否被某標示符修飾。
例如:
0x21的二進制100001 -> ACC_PUBLIC的二進制1 -> 100001&1 = 1 ->是包含ACC_PUBLIC訪問表示符。

類索引
在這裏插入圖片描述
00 03 -> 指向該類的CONSTANT_Class常量 -> 0x0003 -> 3 -> 常量3 結果是com/neo/asmtest/Bytecode/Demo 類索引指向了該類的全限定名。

父類索引
在這裏插入圖片描述
00 04 -> 指向該類的CONSTANT_Class常量 -> 0x0004 -> 4 -> 常量4 結果是java/lang/Object Demo類沒有繼承任何類,所以其默認的父類是Object類。

接口計數器
在這裏插入圖片描述
接口索引集合
接口索引集合是一個集合,包含了所有實現的接口的索引,每個接口索引佔用2個字節,指向常量中的接口。
由於Demo.java沒有實現任何接口,所以不存在這部分的值。

字段個數
在這裏插入圖片描述
00 01 -> 0x0001 -> 之後有1個字段 -> 字段主要用來描述類或者接口中聲明的變量(包含了類級別變量以及實例變量,但是不包括方法內部聲明的局部變量)。

00 00 -> 表示該類實現了幾個接口,即implements了幾個接口。由於Demo沒有實現接口,其值爲0x00 00,轉換爲十進制也爲0。

字段表
在這裏插入圖片描述
結構爲:

field_info{
	u2 access_flags;//訪問標誌	
	u2 name_index;//字段名索引	
	u2 descriptor_index;//描述符索引	
	u2 attributes_count;//屬性計數器	
	attribute_info attributes;//屬性集合	
}

00 02 00 05 00 06 00 00
access_flags = 0x0002 -> 2 -> 0010 -> ACC_PRIVATE -> private
name_index = 0x00 05 -> 5 -> 指向常量5 -> age
descriptor_index = 0x00 06 -> 6 -> 指向常量6 -> I ->表示int類型
attributes_count = 0x00 00 -> 0 -> attribute_info不存在
結果:
private int a

方法計數器
在這裏插入圖片描述
00 02 -> 0x00 02 -> 2個方法
無參構造函數與getAge()。

方法表
結構爲:

method_info{
	u2 access_flags; //方法訪問標誌
	u2 name_index; //方法名稱索引
	u2 descriptor_index; //方法描述符索引
	u2 attributes_count; //屬性計數器	
	struct attribute_info{
		u2 attribute_name_index; //屬性名的索引
		u4 attribute_length; //屬性的長度
		u2 max_stack;//操作數棧深度的最大值
		u2 max_locals;//局部變量表所需的存續空間
		u4 code_length;//字節碼指令的長度
		u1 code; //code_length個code,存儲字節碼指令
		u2 exception_table_length;//異常表長度
		exception_info exception_table;//exception_length個exception_info,組成異常表
		u2 attributes_count;//屬性集合計數器
		attribute_info attributes;//attributes_count個attribute_info,組成屬性表
	}
}

方法1
在這裏插入圖片描述
00 01 00 07 00 08 00 01 00
access_flags = 0x00 01 -> 1 -> public
name_index = 0x00 07 -> 7 -> 指向常量7 ->
descriptor_index = 0x00 08 -> 8 ->指向常量8 -> ()V
attributes_count = 0x00 01 -> 1 -> 記錄着該方法有幾個屬性 -> 1個屬性
00 09 00 00 00 1D 00 01 00 01 00 00 00 05
attribute_name_index = 0x00 09 -> 9 -> 指向常量9 -> Code
attribute_length = 0000001D -> 29 -> 29個字節
max_stack = 0x00 01 -> 1
max_locals = 0x00 01 -> 1
code_length = 00000005 -> 5個字節
2A B7 00 01 B1
code -> 0x2A B7 00 01 B1 ->
不能轉換成十進制取看類,需要把十六進制轉換成指令集。

結果爲:

2A -> aload_0      //從局部變量0中裝載引用類型值入棧
B7 -> invokespecia //編譯時方法綁定調用方法
00 -> nop          //空操作
01 -> aconst_null  //null值入棧
B1 -> return       //void函數返回。

00 00 00 01
exception_table_length = 0x00 -> 0
這裏存放的是處理異常的信息。
每個exception_table表項由start_pc,end_pc,handler_pc,catch_type組成。start_pc和end_pc表示在code數組中的從start_pc到end_pc處(包含start_pc,不包含end_pc)的指令拋出的異常會由這個表項來處理;handler_pc表示處理異常的代碼的開始處。catch_type表示會被處理的異常類型,它指向常量池裏的一個異常類。當catch_type爲0時,表示處理所有的異常,這個可以用來實現finally的功能
attributes_count = 0x00 01 -> 1 -> 表示有一個附加屬性

attribute_info 結構如下:

attribute_info{
    u2 attribute_name_index;
    u4 attribute_length;
    u2 line_tab_table_length
}
line_tab_table_length{
    u2 start_pc;
    u2 line_number;
}

在這裏插入圖片描述
00 0A 00 00 00 06 00 01 00 00 00 03
attribute_name_index = 0x00 0A -> 10 -> 指向常量10 -> LineNumberTable
attribute_length = 0x00 00 00 06-> 6個字節 -> 後面有6個字節的屬性
line_tab_table_length = 0x00 01 -> 1代表LineNumberTable有一項值
start_pc = 0x00 00 -> 0,代表字節碼行號
line_number = 0x 00 03 -> 3 -> 代表java源碼的行號爲第3行

方法2
在這裏插入圖片描述
00 01 00 0B 00 0C 00 01
access_flags = 0x00 01 -> 1 -> public
name_index = 0x00 0B -> 11 -> 指向常量11 -> getAge
descriptor_index = 0x00 0C -> 12 ->指向常量12 -> ()I
attributes_count = 0x00 01 -> 1 -> 記錄着該方法有幾個屬性 -> 1個屬性
00 09 00 00 00 1D 00 01 00 01 00 00 00 05
attribute_name_index = 0x00 09 -> 9 -> 指向常量9 -> Code
attribute_length = 0x0000001D -> 29 -> 29個字節
max_stack = 0x00 01 -> 1
max_locals = 0x00 01 -> 1
code_length = 0x00000005 -> 5個字節
2A B4 00 02 AC
code -> 0x2A B4 00 02 AC ->
不能轉換成十進制取看類,需要把十六進制轉換成指令集
->結果如下:

2A -> aload_0 //從局部變量0中裝載引用類型值入棧
B4 -> getfield // 獲取對象字段的值
00 -> nop // 空操作
02 -> iconst_m1 //-1(int)值入棧
AC -> ireturn //返回int類型值

00 00 00 01

exception_table_length = 0x00 -> 0

attributes_count = 0x00 01 -> 1 -> 表示有一個附加屬性
在這裏插入圖片描述
00 0A 00 00 00 06 00 01 00 00 00 08
attribute_name_index = 0x00 0A -> 10 -> 指向常量10 -> LineNumberTable
attribute_length = 0x00 00 00 06-> 6個字節 -> 後面有6個字節的屬性
line_tab_table_length = 0x00 01 -> 1代表LineNumberTable有一項值
start_pc = 0x00 00 -> 0,代表字節碼行號
line_number = 0x 00 08 -> 8 -> 代表java源碼的行號爲第8行

附加屬性-屬性個數
在這裏插入圖片描述
00 01 -> 1 -> 表示後面有1個附加屬性值。

附加屬性-屬性結構
在這裏插入圖片描述
00 0D 00 00 00 02 00 0E 結構爲:

SourceFile_attribute {
     u2 attribute_name_index;
     u4 attribute_length;
     u2 sourcefile_index;
}

attribute_name_index = 0x00 0D = 13 -> 指向常量13 -> SourceFile
attribute_length = 0x00 00 00 02 = 2 -> 後面有2字節內容
sourcefile_index = 0x00 0E = 14 -> 指向常量14 -> Demo.java
代表着個class字節碼文件的源碼名爲Demo.java。

JVM加載class文件的原理機制?

Java中的所有類,都需要由類加載器裝載到JVM中才能運行。類加載器本身也是一個類,而它的工作就是把class文件從硬盤讀取到內存中。在寫程序的時候,我們幾乎不需要關心類的加載,因爲這些都是隱式裝載的,除非我們有特殊的用法,像是反射,就需要顯式的加載所需要的類。

類裝載方式:
1.隱式裝載: 程序在運行過程中當碰到通過new等方式生成對象時,隱式調用類裝載器加載對應的類到jvm中。
2.顯式裝載, 通過class.forname()等方法,顯式加載需要的類。

Java類的加載是動態的,它並不會一次性將所有類全部加載後再運行,而是保證程序運行的基礎類(像是基類)完全加載到jvm中,至於其他類,則在需要的時候才加載。這當然就是爲了節省內存開銷。

Java的類加載器有三個,對應java三種類:
1.系統類
2.擴展類
3.程序員自定義類

 Bootstrap Loader  // 負責加載系統類 (指的是內置類,像是String,對應於C#中的System類和C/C++標準庫中的類)
        | 
      - - ExtClassLoader   // 負責加載擴展類(就是繼承類和實現類)
                      | 
                  - - AppClassLoader   // 負責加載應用類(程序員自定義的類)

三個加載器各自完成自己的工作,那麼它們是如何協調工作呢?哪一個類該由哪個類加載器完成呢?爲了解決這個問題,Java採用了委託模型機制

委託模型機制的工作原理?

當類加載器需要加載類的時候,先請示其Parent(即上一層加載器)在其搜索路徑載入,如果找不到,纔在自己的搜索路徑搜索該類。這樣的順序其實就是加載器層次上自頂而下的搜索,因爲加載器必須保證基礎類的加載。之所以是這種機制,還有一個安全上的考慮:如果某人將一個惡意的基礎類加載到jvm,委託模型機制會搜索其父類加載器,顯然是不可能找到的,自然就不會將該類加載進來。

獲取類加載器:

ClassLoader loader = ClassName.class.getClassLoader();
ClassLoader ParentLoader = loader.getParent();

注意:
Java在邏輯上並不存在BootstrapKLoader的實體!因爲它是用C++編寫的,所以打印其內容將會得到null。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章