JVM之Class字節碼解析

Class Format

類文件就是由以下兩種類型組成的

u(n) 表示存放n個無符號字節

info 一種複雜結構由無符號和其他表項組成

class二進制流數據組成項

名稱 類型 數量
magic(魔法數) u4 1
minor_version(次版本) u2 1
major_version(主版本) u2 1
constant_pool_count(常量池的個數) u2 1
constant_pool_info(常量池) cp_info constant_pool_count
access_flag u2 1
this_class(當前類,常量池索引) u2 1
super_class(父類) u2 1
interface_count u2 1
interfaces(class 常量索引) u2 interface_count
field_count u2 1
field field_info field_count
method_count u2 1
method method_info method_count
attribute_count u2 1
atrribute attribute_info attribute_count

可分爲:無符號數+表,其中表也是由無符號數+其他表組合的,最終全部都是由無符號數組成的

Byte Ording (字節存儲順序)

在class是以8位一個字節爲基礎二進制流,緊湊排列。

Big-Endia (高位存放在低地址,低位存放在高地址) (注:TCP傳輸的字節順序就採用大端方式,第一次接受到的就是高位字節)

Litte-Endia (高位存放在高地址,低位存放在低地址)

例如一個int類型(4個字節) 採用大端那麼高位存放在前面,低位存放在後面

魔法數:

爲了安全考慮,通過字節碼前2個字節判斷是不是爲CAFEBABE 進而判斷加載的就是類文件,不能通過文件名後綴來判斷,因爲後綴可以隨意改動

版本號:

記錄的是當前編譯成字節碼的jdk版本號,jvm支持向下兼容,1.8版本的jvm可以加載小於等於1.8版本編譯的字節碼。1.8對應於主版本52

常量池:常用的有14種常量類型,通過tag字段來區分

常量池主要存放字面常量和符號引用,字面常量;如final修飾的常量值、字符串

符號引用:類和接口的全限定名,字段名稱和描述符,方法名稱和描述符

UTF-8常量CONSTANT_Utf8_info:tag=1,採用utf-8縮略編碼和普通編碼區別: 如果在’\u0001’到’\u007f’之間的字符串(1-127的ASII碼),從’\u0080’到’\u07ff’之間採用兩個字節,從’\u0800’到’\uffff’之間採用三個字節

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

CONSTANT_Integer_info

採用高位在前存儲int值(Big-Endia 低位在高地址)

名稱 類型 數量
tag u1 1
bytes u4 1

CONSTANT_Long_info

CONSTANT_Float_info

CONSTANT_Double_info

CONSTANT_Class_info

name_index爲常量索引(第幾個常量),指向的就是CONSTANT_Utf8_info的常量 (類或接口的全限定名)

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

CONSTANT_String_info

name_index爲常量索引(第幾個常量),指向的就是CONSTANT_Utf8_info的常量 (字符串字面量索引)

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

CONSTANT_Fieldref_info

存放的是那個類下的那個字段信息

name_index指向聲明字段的類或接口CONSTANT_Class_info索引項

name_type_index 指向聲明字段描述符CONSTANT_Name-AndType_info索引項

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

CONSTANT_Methodref_info

存放的是那個類下的方法信息,name_index 表示爲class_info 類描述符索引項 name_type_index代表方法描述符索引項

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

CONSTANT_Interface-MethodRef_info

name_index 表示爲方法接口的class_info 的索引項

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

CONSTANT_Name-AndType_info

name_index:指向該字段或方法名稱常量項CONSTANT_Utf8_info索引

description_index:指向該字段或方法描述符常量項CONSTANT_Utf8_info索引

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

訪問標誌

修飾類的訪問標誌,ACC_PUBLIC(0x0001) 是否位public類型, ACC_FINAL 是否聲明爲final只有類可以設置,

ACC_ABSTRACT(0x0400) 是否爲抽象類型,只有接口或抽象類爲真

如果該類對應的標誌位爲真,那麼就對應於標誌值,其他就爲0

access_flags 爲 0x0001|0x0400 那麼該類的訪問標誌爲 public abstract

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

類索引和父類索引都是由u2類型的數據,指向的都是 CONSTANT_Class_info的索引項,然後其索引值就可以找到CONSTANT_Utf8_info類型的常量中的全限定名,而接口索引集合是一組u2類型的數據的集合。

類索引用來確定這個類的全限定名

父類索引用來確定父類的全限定名,除了Object類其他類都會有一個父類

接口索引集合,對於入口第一項—u2類型表示爲該類實現(implements)或繼承(extends)接口的數量,後面就是指向class_info索引項集合

字段表集合

與類中的access_flags項目非常類似

name_index 指向字面常量字符串索引,表示字段名稱

descriptor_index 指向字面量常量字符串索引,表示字段描述符號(字段類型)

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

描述符規則:
B: byte

Z: boolean

V: void

L: 對象類型 Ljava/lang/Object

[: 數組類型 [[Ljava/lang/String <> String[][] [I <> int[]

private int a 在class 字節碼存儲的就是 access_flags爲 0x0002(private)

name_index爲0x0005代表第五項常量是一個CONSTANT_Utf8_info類型字符串 值爲m

descriptor_index爲0x0006 代表第六項常量,指向常量池字符串爲I

attributes_count爲0 表示沒有額外的屬性描述信息

如果改成 final static int a = 3 那麼就會存在一項名稱爲ConstantValue 屬性其值指向常量3

注意:

字段表集合中不會列舉超類或繼承的接口中的字段,但是有可能會列舉原本在java代碼不存在的字段,例如在內部類中,爲了保持對外部類的訪問性,會自動添加指向外部類實例的字段。

在java語言中,字段不能重載,不管數據類型或修飾符號是否不同,都必須使用不同的名稱

在jvm字節碼中,只要兩個字段描述符不同,重名是合法的

方法表集合

和字段表結構相同,只不過acces_flags增加了 ACC_SYNCHRONIZED ACC_ABSTRACT 去掉了 transient 和 volatile 標誌位

其中方法裏面的字節碼存放在屬性表中的Code屬性裏面

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

如果父類方法沒有在子類重寫,方法表集合中就不會出現來自父類的方法信息(但是會存在一個虛方法表,此表存放就是父類中所有方法和對應的入口地址,方便動態查找實際類型的方法(多態實現)),但是可能由編譯器自動生成新的方法,例如 類構造器 和實例構造器 方法。

在java語言中,方法的重載,除了方法名相同外,要保證不同特徵簽名, 方法簽名:方法的參數類型,位置和個數,返回值不會包含在方法簽名。

在jvm字節碼中,如果兩個方法有相同名稱和特徵簽名,但返回值不同,那麼也可以合法共存於同個class文件中

屬性表集合

Code屬性表:

javac將源代碼中的方法體代碼編譯成對應的字節碼集存放到方法表中的Code屬性表中,對於接口或抽象類中的抽象方法表就沒有Code屬性

max_stack 代表了操作數棧深度的最大值,例如 方法 A中調用方法B 然後方法B調用方法C那麼 max_stack=3

max_local 代表局部變量所需存儲空間,單位爲Slot,對於byte short int float boolean char 和returnAddress每個局部變量佔有一個Slot,而long double使用兩個Slot存放

code_length和code用來存儲java代碼編譯後生成的字節碼指令, code_length代表字節碼長度,code存放就是u1類型的字節碼序列,一個u1類型取值範圍0-255 表示256條指令,當虛擬機讀取到字節碼時,會到字節碼指令對應關係表中 找到對應的指令。

code_length雖然爲u4 能存放2的32次方-1個字節字節碼,但是虛擬機限制了一個方法不能超過65535條字節碼指令,否則javac編譯不通過。

java程序代碼信息分爲代碼(Code 方法體中java代碼)和元數據(Metadata, 包括類、字段、方法定義以及其他信息 如常量池)兩部分

有一篇對class 文件格式分析的很好的文章JVM字節碼–Class文件格式

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