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文件格式