本文基于周志明的《深入理解java虛擬機 JVM高級特性與最佳實踐》所寫捉超。特此推薦狂秦。
Class文件是一組以8位字節(jié)為基礎單位的二進制流侧啼,各個數(shù)據(jù)項目嚴格按照順序緊湊地排列在Class文件之中痊乾。當遇到需要占用8個字節(jié)以上空間的數(shù)據(jù)項時哪审,則會按照高位在前的方式分割成若干個8位字節(jié)進行存儲滴须。
高位在前:表示各個字節(jié)上的各個bit代表的數(shù)據(jù)的數(shù)位是從高到低扔水。
那普通數(shù)字舉例,
123待德,代表一百二十三,就是高位在前的大端數(shù)
如果它代表是三百二十一涧偷,就是高位在尾的小端數(shù)
8個字節(jié),第1個字節(jié)代表的是數(shù)據(jù)的最高8個bit,即第56到63位再菊。
第2個字節(jié)代表第48-55bit,...第8個字節(jié)代表第0-7位稠诲;
根據(jù)Java虛擬機規(guī)范的規(guī)定臀叙,Class文件格式采用一種類似于C語言結(jié)構(gòu)體的偽結(jié)構(gòu)來存儲,這種偽結(jié)構(gòu)只有兩種數(shù)據(jù)類型:無符號數(shù)和表床嫌。
無符號數(shù)屬于基本的數(shù)據(jù)類型鳖谈,以u1,u2,u4,u8來分別代表1個字節(jié)蚯姆、2個字節(jié)、4個字節(jié)和8個字節(jié)的無符號數(shù)郭毕。
表是由多個無符號數(shù)或其他表作為數(shù)據(jù)項組成的符合數(shù)據(jù)類型。所有表都習慣地以“_info”結(jié)尾乘碑。表用于描述有層次關系的復合結(jié)構(gòu)的數(shù)據(jù),整個Class文件本質(zhì)上就是一張表资铡,由下圖所示的數(shù)據(jù)項構(gòu)成:
演示代碼
package com.sn.Unit6;
public class TestClass {
private int m;
public int inc(){
return m + 1;
}
}
通過二進制工具打開.class文件症副,如下
魔數(shù)與Class文件的版本
Class文件的頭4個字節(jié)稱為魔數(shù)(Magic Number)底洗,它的唯一作用是判斷該文件是否為一個能被虛擬機接受的Class文件。Java的值固定魔數(shù)為0xCAFEBABE费变。緊接著魔數(shù)的4個字節(jié)存儲的是Class文件的版本號挚歧,第5個和第6個字節(jié)是次版本號(Minor Version)在张,第7個和第8個字節(jié)是主版本號(Major Version)。高版本的JDK能向下兼容低版本的Class文件瘟斜,但不能運行更高版本的Class文件。
常量池
緊接著主次版本號之后的是常量池入口蛇尚,常量池是Class文件結(jié)構(gòu)中與其他項目關聯(lián)最多的數(shù)據(jù)類型。常量池之中主要存放兩大類常量:字面量(Literal)和符號引用(Symbolic Reference)。字面量比較接近于Java語言層面的常量概念,如文本字符串灌砖、被聲明為final的常量值等基显。而符號引用則屬于編譯原理方面的概念,包括了下面三類常量:
- 類和接口的全限定名(Fully Qualified Name)
- 字段的名稱和描述符(Descriptor)
- 方法的名稱和描述符宪萄。
Java代碼在進行Javac編譯的時候拜英,并不像C和C++那樣有"連接"這一步驟,而是在虛擬機加載Class文件的時候進行動態(tài)連接。也就是說舆床,在Class文件中不會保存各個方法和字段的最終內(nèi)存布局信息,因此這些字段和方法的符號引用不經(jīng)過轉(zhuǎn)換的話是無法被虛擬機使用的盛垦。當虛擬機運行時,需要從常量池獲得對應的符號引用,再在類創(chuàng)建時或運行時解析并翻譯到具體的內(nèi)存地址之中榨呆。
- 符號引用:符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可丙笋。符號引用與虛擬機實現(xiàn)的內(nèi)存布局無關澳化,引用的目標并不一定已經(jīng)加載到了內(nèi)存中。
- 直接引用:直接引用可以是直接指向目標的指針列林、相對偏移量或是一個能間接定位到目標的句柄。直接引用是與虛擬機實現(xiàn)的內(nèi)存布局相關的砌创,同一個符號引用在不同虛擬機實例上翻譯出來的直接引用一般不會相同。如果有了直接引用,那說明引用的目標必定已經(jīng)存在于內(nèi)存之中了晃洒。
constant_pool_count:占2字節(jié),本例為0x0016桶略,轉(zhuǎn)化為十進制為22惶翻,即說明常量池中有21個常量(只有常量池的計數(shù)是從1開始的纺荧,其它集合類型均從0開始)输枯,索引值為1~22。第0項常量具有特殊意義瞳收,如果某些指向常量池索引值的數(shù)據(jù)在特定情況下需要表達“不引用任何一個常量池項目”的含義,這種情況可以將索引值置為0來表示
constant_pool:表類型數(shù)據(jù)集合插爹,即常量池中每一項常量都是一個表崖面,共有14種(JDK1.7前只有11種)結(jié)構(gòu)各不相同的表結(jié)構(gòu)數(shù)據(jù)娃承。這14種表都有一個共同的特點狸相,即均由一個u1類型的標志位開始闪朱,可以通過這個標志位來判斷這個常量屬于哪種常量類型月匣,常量的含義如下表所示:
這14種常量類型各自均有自己的結(jié)構(gòu)。在CONSTANT_Class_info型常量的結(jié)構(gòu)中有一項name_index屬性锄开,該常屬性中存放一個索引值,指向常量池中一個CONSTANT_Utf8_info類型的常量称诗,該常量中即保存了該類的全限定名字符串萍悴。而CONSTANT_Fieldref_info、CONSTANT_Methodref_info寓免、CONSTANT_InterfaceMethodref_info型常量的結(jié)構(gòu)中都有一項index屬性癣诱,存放該字段或方法所屬的類或接口的描述符CONSTANT_Class_info的索引項。另外袜香,最終保存的諸如Class名撕予、字段名、方法名蜈首、修飾符等字符串都是一個CONSTANT_Utf8_info類型的常量实抡,也因此欠母,Java中方法和字段名的最大長度也即是CONSTANT_Utf8_info型常量的最大長度,在CONSTANT_Utf8_info型常量的結(jié)構(gòu)中有一項length屬性吆寨,它是u2類型的赏淌,即占用2個字節(jié),那么它的最大的length即為65535啄清。因此六水,Java程序中如果定義了超過64KB英文字符的變量或方法名,將會無法編譯盒延。
下表給出了常量池中14種數(shù)據(jù)類型的結(jié)構(gòu):
訪問標志
在常量池結(jié)束之后缩擂,緊接著的2個字節(jié)代表訪問標志(access_flag),這個標志用于識別一些類或接口層次的訪問信息添寺,包括:這個Class是類還是接口胯盯,是否定義為public類型,abstract類型计露,如果是類的話博脑,是否聲明為final,等等票罐。具體標志位含義如下:
類索引叉趣、父類索引與接口索引集合
類索引(this_class)和父類索引(super_class)都是一個u2類型的數(shù)據(jù),而接口索引集合(interfaces)是一組u2類型的數(shù)據(jù)的集合该押,Class文件中由這三項數(shù)據(jù)來確定這個類的繼承關系疗杉。類索引用于確定這個類的全限定名,父類索引用于確定這個類的父類的全限定名蚕礼。Java不允許多重繼承烟具,所以父類索引只有一個,除了java.lang.Object外奠蹬,所有Java類的父類索引都不為0朝聋。接口索引集合就用來描述這個類實現(xiàn)了哪些接口,所有被實現(xiàn)的接口按類定義中的implements(如果類是一個接口則是extends)后的接口順序從左到右排列在接口的索引集合中囤躁。
字段表集合
字段表(field_info)用于描述接口或類中聲明的變量冀痕。字段(field)包括了類級變量和實例級變量,但不包括方法內(nèi)部聲明的變量狸演。一個字段的信息包括:作用域(public言蛇、private、protected修飾符)宵距、是實例變量還是類變量(static修飾符)猜极、可變性(final)、并發(fā)可見性(volatile修飾符消玄,是否強制從主內(nèi)存讀寫)跟伏、可否序列化(transient修飾符)、字段數(shù)據(jù)類型(基本數(shù)據(jù)類型翩瓜、對象受扳、數(shù)組)、字段名稱兔跌。這些信息中勘高,各個修飾符都是布爾值,要么有坟桅,要么沒有华望。而字段的名稱與定義,只能引用常量池中的常量描述仅乓。下圖表示字段表結(jié)構(gòu):
字段修飾符放在access_flags項目中赖舟,是一個u2數(shù)據(jù)類型,下圖表示其含義:
實際情況中夸楣,ACC_PUBLIC宾抓、ACC_PRIVATE和ACC_PROTECTED這三個標志最多只能選擇其一,接口中的字段必須有ACC_PUBLIC豫喧、ACC_STATIC石洗、ACC_FINAL標志,這些都是java語言所要求的紧显。
name_index:常量池的引用讲衫,代表字段的簡單名稱。
descriptor_index:常量池的引用孵班,代表字段和方法的描述符涉兽。
1,全限定名:將類全名中的“.”替換為“/”重父,為了保證多個連續(xù)的全限定名之間不產(chǎn)生混淆花椭,在最后加上“;”表示全限定名結(jié)束。例如:"com.test.Test"類的全限定名為"com/test/Test;"
3郭厌,描述符:描述字段的數(shù)據(jù)類型袋倔、方法的參數(shù)列表(包括數(shù)量、類型和順序)和返回值折柠。根據(jù)描述符規(guī)則宾娜,基本數(shù)據(jù)類型和代表無返回值的void類型都用一個大寫字符表示,而對象類型則用字符L加對象全限定名表示
2房午,簡單名稱:沒有類型和參數(shù)修飾的方法或字段名稱矿辽。例如:"public void add(int a,int b){...}"該方法的簡單名稱為"add","int a = 123;"該字段的簡單名稱為"a"
對于數(shù)組類型扇售,每一維將使用一個前置的“[”字符來描述前塔,如:"int[]"將被記錄為"[I","String[][]"將被記錄為"[[Ljava/lang/String;"描述符標識字符含義
用描述符描述方法時嚣艇,按照先參數(shù)列表,后返回值的順序描述华弓,參數(shù)列表按照參數(shù)的嚴格順序放在一組"()"之內(nèi)食零,如:方法"String getAll(int id,String name)"的描述符為"(I,Ljava/lang/String;)Ljava/lang/String;"
方法表集合
Class文件存儲格式中對方法的描述與對字段的描述幾乎完全一致。方法表的結(jié)構(gòu)如同字段表一樣寂屏,一次包括了訪問標志贰谣、名稱索引、描述符索引迁霎、屬性表集合幾項吱抚。由于ACC_VOLATILE標志和ACC_TRANSIENT標志不能修飾方法,所以access_flags中不包含這兩項考廉,同時增加ACC_SYNCHRONIZED標志秘豹、ACC_NATIVE標志、ACC_STRICTFP標志和ACC_ABSTRACT標志
屬性表集合
Class文件芝此、字段表和方法表都可以攜帶自己的屬性信息憋肖,這個信息用屬性表進行描述,用于描述某些場景專有的信息婚苹。
與Class文件中其它數(shù)據(jù)項對長度岸更、順序、格式的嚴格要求不同膊升,屬性表集合不要求其中包含的屬性表具有嚴格的順序怎炊,并且只要屬性的名稱不與已有的屬性名稱重復,任何人實現(xiàn)的編譯器可以向?qū)傩员碇袑懭胱约憾x的屬性信息廓译。虛擬機在運行時會忽略不能識別的屬性评肆,為了能正確解析Class文件,虛擬機規(guī)范中預定義了虛擬機實現(xiàn)必須能夠識別的9項屬性(預定義屬性已經(jīng)增加到21項):