Class文件中存儲著Java虛擬機指令集和符號表以及若干輔助信息。它使用的是一種平臺無關的字節(jié)碼儲存格式,不同的虛擬機實現(xiàn)都可以載入執(zhí)行這種平臺無關的字節(jié)碼桐猬。Java虛擬機不與任何語言綁定插龄,只與Class文件這種特定二進制文件格式關聯(lián),原則上任何語言都可以編譯成Class文件在Java虛擬機上運行招盲。
一、 Class類文件結構
Class文件格式采用一種類似C語言結構體的偽結構來存儲數(shù)據(jù)嘉冒,這種偽結構中只有兩種數(shù)據(jù)類型:無符號數(shù)和表曹货。
1.1 無符號數(shù)
無符號數(shù)屬于基本數(shù)據(jù)類型咆繁,以u1,u2顶籽,u4玩般,u8來分別代表1個字節(jié)、2個字節(jié)礼饱、4個字節(jié)和8個字節(jié)的無符號數(shù)壤短,無符號數(shù)可以用來描述數(shù)字、索引引用慨仿、數(shù)量值或者按照UTF-8編碼構成字符串值久脯。
1.2 表
表是由多個無符號數(shù)或者其他表作為數(shù)據(jù)項構成的復合數(shù)據(jù)類型。表用于描述有層次關系的復合結構的數(shù)據(jù)镰吆,整個Class文件本質(zhì)上就是一張表帘撰。Class中以_info結尾代表一張表。
二万皿、Class字節(jié)碼解析
Class文件是一組以8位字節(jié)為基礎單位的二進制流摧找,各個數(shù)據(jù)項目嚴格的按照順序緊湊的排列在Class文件中。當遇到占用8字節(jié)以上空間的數(shù)據(jù)項時牢硅,則會按照高位在前的方式分割成若干個8位字節(jié)進行存儲蹬耘。Class內(nèi)部不包含任何分隔符,數(shù)據(jù)存儲順序數(shù)量都被嚴格限定减余,不允許任何改動综苔。下面看看具體數(shù)據(jù)項的含義:
2.1 魔數(shù)(Magic Number)
每個Class文件的頭4個字節(jié)稱為魔數(shù)(Magic Number),它唯一作用就是用來確定文件是否能被虛擬機接受位岔。
很多文件存儲標準中都用魔數(shù)進行身份標識如筛,如圖片gif,jpeg都在文件頭部中存儲著魔數(shù)抒抬。使用魔數(shù)而不是用擴展名來進行識別主要是基于安全考慮杨刨,因為擴展名可以被隨意改動。
2.2 版本號
接下來的4個字節(jié)存儲著Class文件的版本號擦剑,第五第六個字節(jié)為次版本號(Minor Version)妖胀,第七第八為主版本號(Major Version)。版本號主要用于版本控制惠勒,高版本的JDK能向下兼容以前版本的Class文件赚抡,但不能運行以后版本的Class文件。
2.3 常量池入口
緊接著版本號之后的就是常量池入口捉撮,常量池入口后面還必須有一個u2數(shù)據(jù)項作為常量池容量計數(shù)器(因為常量池數(shù)量不固定)怕品。
常量池是一個表類型的數(shù)據(jù)項妇垢,相當于Class文件的資源倉庫巾遭,與Class文件其他項目關聯(lián)最多肉康,占用Class空間最大的數(shù)據(jù)項之一,且是第一個出現(xiàn)的表類型數(shù)據(jù)項目灼舍。
常量池主要存儲兩大類常量:字面量(Literal)和符號引用(Symbolic References)
字面量相當于Java語言中的常量概念吼和,比如字符串,聲明為final的常量值骑素。
符號引用則屬于編譯原理方面的概念包括三類常量:
- 類和接口的全限定名(Fully Qualified Name)
- 字段的名稱和描述符(Descriptor)
- 方法的名稱和描述符
Class文件不會保存各個方法字段的最終內(nèi)存布局信息炫乓,因為這些字段、方法和符號引用不經(jīng)過運行期轉換(動態(tài)連接)的話無法得到真正內(nèi)存入口地址献丑,也就無法被虛擬機使用末捣。當虛擬機運行時,需要從常量池獲得對應的符號引用创橄,再在類創(chuàng)建或運行時解析翻譯到具體的內(nèi)存地址之中箩做。
常量池中的每一項常量都是一個表(JDK1.7中有14種)。包括UTF-8編碼的字符串表妥畏,整型字面量表邦邦,浮點型字面量表,長整型字面常量表醉蚁,類和接口的符號引用表燃辖,字段符號引用表,類中的方法符號引用表网棍,接口中方法符號引用表等等黔龟。這些表都會有各自不同的結構。
2.4 訪問標志
常量池之后就是由兩個字節(jié)代表的訪問標識(access flags)這些標識用于識別一些類或者接口層次的訪問信息滥玷,包括這個Class是類還是接口捌锭;是否定義為public;是否定義為abstract類型罗捎;是否被final修飾观谦。
2.5 類索引、父類索引桨菜、接口索引
訪問標志位之后就是u2類型的類索引豁状,父類索引和接口索引集合。Class文件由這三項數(shù)據(jù)確定這個類的繼承關系倒得。這三項數(shù)據(jù)(u2類型的索引值)各指向類型為CONSTANT_Class_info的類描述符常量泻红。
2.6 字段表集合
字段表用于描述接口或者類中聲明的變量。字段(field)包括類級變量以及實例級變量霞掺,但不包括在方法內(nèi)部聲明的局部變量谊路。字段表中字段的各種描述信息(作用域比如public,private菩彬,是否被final缠劝,static修飾潮梯,是否可序列化等)均使用標志位表示,名稱則引用常量池中的常量來描述惨恭。
2.7 方法表集合
在方法表中秉馏,方法的描述和字段的描述基本一致,依次包括訪問標志(access_flags)脱羡、名稱索引(name_index)萝究、描述符索引(descriptor_index)、屬性表集合(attributes)幾項锉罐。
方法中的代碼經(jīng)過編譯器編譯成字節(jié)碼指令后存放在方法屬性表集合中一個名為“Code”的屬性里面帆竹。
如果父類方法在子類中沒有被重寫,方法表集合中就不會出現(xiàn)來自父類的方法信息脓规。
2.8 屬性表集合
Class文件馆揉、字段表、方法表都可以攜帶自己的屬性表集合抖拦,以用于描述某些場景專有的信息升酣。
為了能正確解析Class文件,在Java SE 7中預定義了21項屬性态罪,虛擬機在運行時會忽略他不認識的屬性噩茄。
三、字節(jié)碼指令
Java虛擬機的指令是由一個字節(jié)長度的复颈、代表著某種特定操作含義的數(shù)字(操作碼绩聘,Opcode)以及跟隨其后的零個至多個代表此操作所需要的參數(shù)(操作數(shù),Operands)構成耗啦。
常用指令:
- 加載存儲指令凿菩,將數(shù)據(jù)在棧幀中的局部變量表和操作數(shù)棧之間來回傳輸
- 運算指令,對兩個操作數(shù)棧上的值進行某種特定運算帜讲,并把結果重新寫入操作棧衅谷,包括加減乘除邏輯與或非
- 類型轉換指令,將兩種不同的數(shù)值類型進行相互轉換似将,這些轉換一般用于實現(xiàn)用戶代碼中的顯式類型轉換操作获黔,或者用來處理字節(jié)碼指令集中數(shù)據(jù)類型相關指令代碼無法與數(shù)據(jù)類型一一對應的問題。另在验,類型轉換指令永遠不可能導致虛擬機運行異常玷氏。
- 對象創(chuàng)建與訪問指令,創(chuàng)建對象數(shù)組訪問對象等
- 操作數(shù)棧管理指令腋舌,Java虛擬機提供了一些用于直接操作操作數(shù)棧的指令盏触,包括入棧,出棧,棧頂端兩個數(shù)據(jù)交換
- 控制轉移指令赞辩,可以讓Java虛擬機有條件或無條件地從指定的位置指令而不是控制轉移指令的下一條指令繼續(xù)執(zhí)行程序雌芽,包括條件分支ifxxx,復合條件分支诗宣,無條件分支goto等膘怕。
- 方法調(diào)用和返回指令想诅,調(diào)用對象實例方法召庞,調(diào)用接口方法,調(diào)用類方法来破,運行時動態(tài)解析出調(diào)用點限定符所引用的方法篮灼,返回指令根據(jù)返回值類型區(qū)分
- 異常處理指令,Java程序中顯式拋出異常的操作(throw)都是由athrow指令實現(xiàn)徘禁,異常處理不是字節(jié)碼指令實現(xiàn)诅诱,而是采用異常表實現(xiàn)
- 同步指令,方法級同步和方法內(nèi)部一段指令序列的同步(通過管程Monitor支持)送朱,執(zhí)行線程要求先持有管程娘荡,然后才能執(zhí)行方法,當方法執(zhí)行完成后釋放管程驶沼;方法執(zhí)行期間炮沐,執(zhí)行線程持有管程,任何一個線程都無法再獲取同一個管程