Java Class文件結(jié)構(gòu)

引言

我們都知道java是跨平臺的偎快,原因就在于各個平臺的java虛擬機(jī)可以載入和執(zhí)行同一種平臺無關(guān)的字節(jié)碼文件,也就是說java虛擬機(jī)不與包括Java在內(nèi)的任何語言綁定辆琅,只于Class文件這種二進(jìn)制格式文件所關(guān)聯(lián)笙什。

基于這樣的設(shè)計即供,到目前為止已經(jīng)出現(xiàn)了很多基于Java虛擬機(jī)的語言

groovy最終都會編譯成class文件

Class文件結(jié)構(gòu)

一個Class文件唯一對應(yīng)一個類或接口

現(xiàn)在讓我們來看下Class文件的基本結(jié)構(gòu)

Class文件以8位字節(jié)為基本單位的二進(jìn)制文件吨悍,各個數(shù)據(jù)項目嚴(yán)格的按照順序緊湊的排列在Class文件之中扫茅,中間沒有任何分隔符,這使得整個Class文件中存儲的內(nèi)容幾乎全部是運(yùn)行時的必要數(shù)據(jù)

Class文件的二進(jìn)制文件只有兩種數(shù)據(jù)類型:無符號數(shù)和表育瓜,后面的解析都會以這兩種數(shù)據(jù)類型為基礎(chǔ)

  • 無符號數(shù)

無符號數(shù)是最基本的數(shù)據(jù)類型葫隙,以u1,u2,u4,u8來分別代表1個字節(jié),2個字節(jié)躏仇,4個字節(jié)恋脚,8個字節(jié)的無符號數(shù),無符號數(shù)用來描述數(shù)字焰手,索引引用糟描,數(shù)量值,以及以UTF-8編碼的字符串

表則是由多個無符號數(shù)或其他表組成的復(fù)合數(shù)據(jù)結(jié)構(gòu)书妻,表的名稱一般以_info結(jié)尾蚓挤。所以整個Class文件其實就是一張?zhí)厥獾谋?/strong>

下面表中所列的就是一個Class按順序排列的數(shù)據(jù)結(jié)構(gòu)

類型 名稱 數(shù)量
u4 magic 魔數(shù) 標(biāo)識Class文件 1
u2 minor_version 次版本號 1
u2 major_version 主版本號 1
u2 constant_pool_count 常量表集合數(shù)量 1
cp_info constant_pool 常量表 constant_pool_count - 1
u2 access_flag 訪問標(biāo)識 1
u2 this_class 類索引 1
u2 super_class 父類索引 1
u2 interfaces_count 接口索引數(shù)量 1
u2 interfaces 接口索引 interfaces_count
u2 fields_count 字段表集合數(shù)量 1
field_info fields 字段表 fields_count
u2 methods_count 方法表集合數(shù)量 1
method_info methods 方法表 methods_count
u2 attributes_count 屬性表集合數(shù)量 1
attribute_info attributes 屬性表 attributes_count

下面依次來解讀表中每個類型

魔數(shù)和版本號

頭4個字節(jié)稱為Class文件的魔數(shù),魔數(shù)的作用是標(biāo)識此文件能被Java虛擬機(jī)接受的Class文件驻子,其實不止Class文件有魔數(shù)這個概念,包括其他很多文件格式出于安全的考慮也都會有魔數(shù)這個概念估灿,魔數(shù)都是固定不變的崇呵,如Class文件的魔數(shù)就是cafebabe

緊接著魔數(shù)之后的是版本號,第5 6個字節(jié)表示的是次版本號馅袁,第7 8個字節(jié)表示的是主版本號域慷。版本號都是向下兼容的

常量池表

讀懂常量池表對于閱讀Class字節(jié)碼非常重要,下面我們將以大篇幅分析常量池表

常量池是Class文件中出現(xiàn)的第一個表結(jié)構(gòu)類型,同時也是占用Class文件最大空間的類型之一犹褒。由于常量池表的數(shù)量不是固定的抵窒,所以在常量池的入口有一項u2類型的數(shù)據(jù),來代表常量池的數(shù)量叠骑。并且常量池比較特殊李皇,容量計數(shù)是從1開始而不是從0開始,所以實際的常量池數(shù)量是constant_pool_count - 1

常量池中主要存放兩大類變量: 字面量和符號引用宙枷。字面量類似常量的概念掉房,而符號引用則引至編譯原理的概念,包括三類(類和接口的全限定名慰丛,字段的名稱和描述符卓囚,方法的名稱和描述符),這里要注意的是诅病,Java在javac編譯的時候不會進(jìn)行Class文件的動態(tài)連接哪亿,只有在運(yùn)行時才會進(jìn)行具體的Class文件的解析操作

常量池分類類型

常量池表結(jié)構(gòu)

上文兩大類的常量池類型細(xì)分之后,到JDK1.7之后增加到了14種贤笆。之所以說常量池是最復(fù)雜的結(jié)構(gòu)蝇棉,就是因為這14種不同的類型都有不同的表結(jié)構(gòu),下面我們來簡單看下這14種結(jié)構(gòu)

每種常量類型的起始位都有一個u1類型的tag標(biāo)識符苏潜,用于標(biāo)識當(dāng)前的常量類型

截圖至深入理解java虛擬機(jī)

截圖至深入理解java虛擬機(jī)

訪問標(biāo)志

訪問標(biāo)志用于識別類或接口層面的信息银萍,標(biāo)識是否為publicabstract恤左,final贴唇,注解枚舉

類索引 父類索引 接口索引

類索引和父類索引都是一個u2類型的數(shù)據(jù)飞袋,而接口索引則是一組u2類型的集合戳气,所以接口索引入口的第一項為一個u2類型的計數(shù),表示有幾個接口索引

類索引和接口索引的具體值是一個u2的數(shù)據(jù)項巧鸭,并且指向一個CONSTANT_Class_info常量池表類型在常量池中的偏移量

字段表集合

字段表用于描述接口或類中聲明的變量瓶您,包括類級變量和實際級變量,不包括在方法內(nèi)部聲明的局部變量

包含的信息主要有這幾種: 字段作用域(private,protact,public)纲仍,是否static修飾呀袱,可變性volatile 修飾郑叠,可否被序列化夜赵,字段類型(基本類型,引用乡革,數(shù)組)寇僧,字段名稱

字段表結(jié)構(gòu)

類型 名稱 數(shù)量
u2 access_flas 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info 屬性表 attributes_count

access_flas

我們來看下字段access_flas訪問標(biāo)識可選的類型

標(biāo)志名稱 標(biāo)志值 含義
ACC_PUBLIC 0x0001 字段是否public
ACC_PRIVARE 0x0002 字段是否private
ACC_PROTECTED 0x0004 字段是否protected
ACC_STATIC 0x0008 字段是否static
ACC_FINAL 0x0010 字段是否final
ACC_VOLATILE 0x0040 字段是否volatile
ACC_TRANSIENT 0x0080 是否 transient
ACC_SYNTHETIC 0x1000 字段由編譯器自動產(chǎn)生
ACC_ENUM 0x4000 字段是否為枚舉類型

看個例子摊腋,如果access_flas0x0019,則標(biāo)識了ACC_PUBLIC嘁傀,ACC_STATIC兴蒸,ACC_FINAL三種類型

name_index和descriptor_index

跟在access_flas之后是name_index(簡單名稱)descriptor_index(描述符)。包括之前出現(xiàn)的全限定名细办,這里解釋一下這幾個名稱橙凳。全限定名稱一般來說是指org/xxx/TestClass這種類型的名稱,可以理解為類的路徑蟹腾,簡單名稱就更加容易理解了痕惋,例如方法inc()的簡單名稱值的就是inc

描述符相對于上面的兩種稍復(fù)雜一些,描述符的作用是用來描述字段的數(shù)據(jù)類型娃殖,方法的參數(shù)列表和返回值值戳,根據(jù)描述符規(guī)則,基本數(shù)據(jù)類型(byte,char,int,long,float,double,short,boolean)以及代表無返回值的void類型都用一個大寫字符來表示炉爆,而對象類型則用L加對象的全限定名來表示

具體的列在了下表中

標(biāo)識字符 含義
B byte
C char
D double
F float
I int
J long
S short
Z boolean
V void
L 對象類型 如 Ljava/lang/Object

對于數(shù)組類型堕虹,每一緯度使用一個前置的[來表示,如定義一個java.lang.String []的數(shù)組類型芬首,將被記錄為[[Ljava/lang/String;

用描述符來描述方法時赴捞,按照先參數(shù)列表后返回值的順序描述,參數(shù)列表嚴(yán)格按照順序放在()內(nèi)郁稍,如方法void inc()的描述符為()V赦政,方法int inc(int i, double)的描述符為(ID)I

在描述符之后,緊跟著是一個屬性表集合耀怜,屬性表集合可以為空恢着,

方法表

方法表的組成與屬性表的組成是完全一致的,訪問標(biāo)識符的取值略有不同

標(biāo)志名稱 標(biāo)志值 含義
ACC_PUBLIC 0x0001 方法是否public
ACC_PRIVARE 0x0002 方法是否private
ACC_PROTECTED 0x0004 方法是否protected
ACC_STATIC 0x0008 方法是否static
ACC_FINAL 0x0010 方法是否final
ACC_SYNCHRONIZED 0x0020 方法是否同步
ACC_BRIDGE 0x0040 方法是否由編譯器產(chǎn)生的橋接方法
ACC_VARARGS 0x0080 方法是否接受不定蠶食
ACC_NATIVE 0x0100 方法是否為native
ACC_ABSTRACT 0x0400 方法是否為abstract
ACC_STRICTFP 0x0800 方法是否為strictfp
SYNTHETIC 0x1000 方法由編譯器自動產(chǎn)生

那么這里大家可能會有疑問财破,方法里的java代碼去哪了呢掰派? 答案就是在方法表的屬性表集合中,有一個code屬性左痢,那里存放了編譯成字節(jié)碼的Java代碼靡羡。對于屬性表,在下文會提到

屬性表

屬性表俊性,前文已經(jīng)提到了多次略步。包括Class文件本身,方法表定页,字段表都有攜帶自己的屬性表集合纳像,用于描述專有場景信息

并且屬性表與Class文件其他數(shù)據(jù)項要求不同,各個屬性不要求嚴(yán)格的順序拯勉,并且只要不與已有屬性名重復(fù)竟趾,任何人實現(xiàn)的編譯器都可以向?qū)傩员碇袑懭胱约憾x的屬性信息

下面我們來看幾個關(guān)鍵屬性

code 屬性

java程序方法體中的代碼javac編譯器處理后,最終變?yōu)樽止?jié)碼子令存儲在Code屬性內(nèi)

Exceptions 屬性

列舉方法可能會拋出的異常

LineNumberTable 屬性

描述java源碼行數(shù)和字節(jié)碼行數(shù)的對應(yīng)關(guān)系宫峦,前者是字節(jié)碼行岔帽,后者是源碼行

LocalVariableTable 屬性

描述棧局部變量和源碼中定義的變量的關(guān)系.這項是可選的,可使用javac -g:nonejavac -g:vars命令關(guān)閉生成這項信息

SourceFile 屬性

用于記錄Class源碼文件的文件名稱导绷,這個屬性是可選的犀勒。可使用javac -g:nonejavac -g:source命令關(guān)閉生成這項信息

ConstantValue 屬性

通知虛擬機(jī)為靜態(tài)變量賦值

InnerClasses 屬性

用于記錄內(nèi)部類與宿主類之間的關(guān)系

Deprecated Synthetic 屬性

Deprecated 標(biāo)識某個類妥曲,字段或方法過期
Synthetic 標(biāo)識此字段不由Java源碼直接產(chǎn)生贾费,由編譯器自動添加

StackMapTable 屬性

這個屬性會在虛擬機(jī)加載完字節(jié)碼后的驗證階段被使用

Signature 屬性

SignatureJDK1.5之后被添加,用于記錄泛型簽名信息檐盟。之所以要用這么一個屬性去記錄泛型信息褂萧,是因為Java語言的泛型采用的是擦除法實現(xiàn)的偽泛型,在Code屬性中葵萎,泛型信息在編譯之后統(tǒng)統(tǒng)都被擦除掉了导犹。使用的擦除法的原因是這樣子實現(xiàn)比較簡單,只需要修改javac編譯器就可以實現(xiàn)了羡忘,運(yùn)行時也可以節(jié)省一些空間谎痢。壞處就是運(yùn)行時無法拿到泛型信息。Signature就是為了彌補(bǔ)這個缺陷而設(shè)置的卷雕,現(xiàn)在的Java API反射能夠獲取到的泛型信息也來自這個屬性

BootStrapMathods 屬性

BootStrapMathods是JDK 1.7之后增加到規(guī)范中的节猿,這個屬性用于保存invokedynamic指令引用的引導(dǎo)方法限定符。本篇文章暫不贅述這個指令

博客原文戳這里

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末漫雕,一起剝皮案震驚了整個濱河市滨嘱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蝎亚,老刑警劉巖九孩,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異发框,居然都是意外死亡躺彬,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門梅惯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宪拥,“玉大人,你說我怎么就攤上這事铣减∷” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵葫哗,是天一觀的道長缔刹。 經(jīng)常有香客問我球涛,道長,這世上最難降的妖魔是什么校镐? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任亿扁,我火速辦了婚禮,結(jié)果婚禮上鸟廓,老公的妹妹穿的比我還像新娘从祝。我一直安慰自己炮沐,他們只是感情好氨菇,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著滥朱,像睡著了一般员咽。 火紅的嫁衣襯著肌膚如雪毒涧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天骏融,我揣著相機(jī)與錄音链嘀,去河邊找鬼。 笑死档玻,一個胖子當(dāng)著我的面吹牛怀泊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播误趴,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼霹琼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了凉当?” 一聲冷哼從身側(cè)響起枣申,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎看杭,沒想到半個月后忠藤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡楼雹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年模孩,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贮缅。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡榨咐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谴供,到底是詐尸還是另有隱情块茁,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站数焊,受9級特大地震影響永淌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜昌跌,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一仰禀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蚕愤,春花似錦、人聲如沸饺蚊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽污呼。三九已至裕坊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間燕酷,已是汗流浹背籍凝。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留苗缩,地道東北人饵蒂。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像酱讶,于是被迫代替她去往敵國和親退盯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容