JVM(四):類文件結(jié)構(gòu)

Java程序運行在Java虛擬機上,實現(xiàn)平臺無關(guān)性自娩。
其它語言的應(yīng)用程序也可以運行在Java虛擬機上款熬,實現(xiàn)語言無關(guān)性。
平臺無關(guān)性和語言無關(guān)性的基礎(chǔ)就是虛擬機字節(jié)碼(Class文件)婉称。

Java語言中的各種變量块仆、關(guān)鍵字和運算符號的定義最終都是由多條字節(jié)碼命令組合而成,因此字節(jié)碼命令提供的語義描述能力比Java語言本身更強大王暗。

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

任何一個Class文件都對應(yīng)唯一的一個類或接口定義悔据,但類或接口不一定都得定義在文件里(類加載器可直接生成類或接口)。

Class文件是一組以8位字節(jié)為基礎(chǔ)單位二進制流俗壹,各個數(shù)據(jù)項目嚴格按照順序緊湊地排列在Class文件中科汗,中間沒有任何分隔符。當某個數(shù)據(jù)項需要占用8位字節(jié)以上的空間時绷雏,則按照高位在前的方式分隔成若干個8位字節(jié)進行存儲头滔。

Class文件中定義了兩種數(shù)據(jù)類型:

  • 無符號數(shù)
    無符號數(shù)用來描述數(shù)字、索引引用涎显、數(shù)量值或者按照UTF-8編碼的字符串值坤检,以u1、u2期吓、u4缀蹄、u8表示1個字節(jié)、2個字節(jié)膘婶、4個字節(jié)缺前、8個字節(jié)的無符號數(shù)。


  • 表用于描述有層次關(guān)系的復(fù)合結(jié)構(gòu)類型悬襟,由多個無符號數(shù)或其他表作為數(shù)據(jù)項構(gòu)成的復(fù)合數(shù)據(jù)類型衅码,習慣性以“_info”結(jié)尾。

1. Class文件格式

類型 名稱 數(shù)量
u4 magic 1
u2 minor_version 1
u2 major_version 1
u2 constant_pool_count 1
cp_info constant_pool constant_pool_count-1
u2 access_flags 1
u2 this_class 1
u2 super_class 1
u2 interfaces_count 1
u2 interfaces interfaces_count
u2 fields_count 1
field_info fields fields_count
u2 methods_count 1
method_info methods methods_count
u2 attributes_count 1
attribute_info attributes attributes_count

2. 說明

  • 魔數(shù)(magic)

Class文件的前4個字節(jié)脊岳,用來確定這個文件是否為一個能被虛擬機接受的Class文件逝段。
Java的Class文件魔數(shù)為0xCAFEBABE


  • 次版本號(minor_version)割捅、主版本號(major_version)

隨JDK發(fā)行而不斷增加奶躯。JDK能向下兼容以前版本的Class文件,但是不能運行之后版本的Class文件亿驾。


  • 常量池(constant_pool)

常量池是Class文件中的資源倉庫嘹黔,是占用Class文件空間最大的數(shù)據(jù)項目之一。

常量池中常量的數(shù)量是不固定的莫瞬,因此需要在常量池的入口處加入一個u2類型的數(shù)據(jù)來表示常量池中常量個數(shù)儡蔓。
常量池中的第0項特意被空出來不存放常量郭蕉,當要表達“不引用任何一個常量池項目”時,就可以將索引值置為0來表示了喂江。因此常量池中存放的常量是從第1項開始的召锈,所以常量池中常量個數(shù)為constant_pool_count-1。

常量池中存放的常量主要包括:
(1)字面量:文本字符串获询、聲明為final的常量值等涨岁。
(2)符號引用:類和接口的全限定名、字段的名稱和描述符吉嚣、方法的名稱和描述符

這些常量都是表梢薪,被分別定義為如下14中表結(jié)構(gòu):

類型 標志 描述
CONSTANT_Utf8_info 1 UTF-8編碼的字符串
CONSTANT_Integer_info 3 整型字面量
CONSTANT_Float_info 4 浮點型字面量
CONSTANT_Long_info 5 長整型字面量
CONSTANT_Double_info 6 雙精度浮點型字面量
CONSTANT_Class_info 7 類或接口的符號引用
CONSTANT_String_info 8 字符串類型字面量
CONSTANT_Fieldref_info 9 字段的符號引用
CONSTANT_Methodref_info 10 類中方法的符號引用
CONSTANT_InterfaceMethodref_info 11 接口中方法的符號引用
CONSTANT_NameAndType_info 12 字段或方法的部分符號引用
CONSTANT_MethodHandle_info 15 方法句柄
CONSTANT_MethodType_info 16 方法類型
CONSTANT_InvokeDynamic_info 18 動態(tài)方法調(diào)用點

這些表結(jié)構(gòu)的第一位都是一個u1的標志位,根據(jù)此標志位值可以確定該常量的表結(jié)構(gòu)是什么樣的瓦戚,然后才能正確解析沮尿。

javap -verbose ClassName可以解析名為ClassName的class文件字節(jié)碼內(nèi)容。


  • 訪問標志(access_flags)

訪問標志用于識別類/接口層次的訪問信息较解,例如這個Class是類還是接口畜疾,是否定義為public,是否為abstract類型等印衔。


  • 類索引(this_class)啡捶、父類索引(super_class)、接口索引集合(interfaces)

Class文件中這三項數(shù)據(jù)用于確定這個類的繼承關(guān)系奸焙。
類索引:確定這個類的全限定名瞎暑;
父類索引:確定這個類的父類的全限定名;
索引集合:描述這個類實現(xiàn)了哪些接口与帆;

查找全限定名的過程:


  • 字段表集合(fields)

字段表用于描述接口或類中聲明的變量了赌,包括類級變量、實例級變量玄糟,但不包括方法內(nèi)部聲明的局部變量勿她。
字段表集合中不會列出從超類或者父接口中繼承而來的字段。

類型 名稱 數(shù)量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count

(1)access_flags用于描述字段作用域(public阵翎、private逢并、protected)、實例變量還是類變量(static)郭卫、可變性(final)砍聊、并發(fā)可見性(volatile)、可否被序列化(transient)等信息贰军。
(2)name_index是對常量池的引用玻蝌,常量池中存放實際的字段的簡單名。
(3)descriptor_index也是對常量池的引用,常量池中存放實際的字段的描述符灶伊。

描述符:

標識字符 含義
B 基本類型byte
C 基本類型char
D 基本類型double
F 基本類型float
I 基本類型int
J 基本類型long
S 基本類型short
Z 基本類型boolean
V 特殊類型void
L 對象類型
[ 數(shù)組

舉例:
java.lang.String[][] 描述為[[Ljava/lang/String;
int[] 描述為[I


  • 方法表集合(methods)

結(jié)構(gòu)與字段表集合類似疆前。
父類方法在子類中沒有被重載寒跳,那么方法表集合中就沒有來自父類的方法信息聘萨。

類型 名稱 數(shù)量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count

為什么Java中不能根據(jù)返回值不同來重載方法?
由方法表集合結(jié)構(gòu)可知童太,Java的方法特征簽名中只有方法名米辐、參數(shù)順序和參數(shù)類型,不包括返回值书释,所以不能用返回值對方法進行重載翘贮。

  • 屬性表集合(attributes)

在Class文件中,屬性表集合用于描述某些場景專有的信息爆惧。
字段表和方法表的最后就是使用屬性表來描述一些額外信息的狸页。

屬性表結(jié)構(gòu):

類型 名稱 數(shù)量
u2 attribute_name_index 1
u4 attribute_length 1
自定義 info 1

attribute_name_index是一個指向CONSTANT_Utf8_info型常量的索引,表示屬性名扯再。
attribute_length指示了屬性值占用的位數(shù)芍耘。
info是屬性值的結(jié)構(gòu),完全自定義熄阻。

舉例:
(1)Code屬性
Java程序的方法體內(nèi)的代碼經(jīng)過javac編譯后斋竞,變?yōu)樽止?jié)碼指令存儲在Code屬性內(nèi),Code屬性在方法表的屬性表集合中秃殉。

類型 名稱 數(shù)量
u2 attribute_name_index 1
u4 attribute_length 1
u2 max_stack 1
u2 max_locals 1
u4 code_length 1
u1 code code_length
u2 exception_table_length 1
exception_info exception_table exception_table_length
u2 attributes_count 1
attribute_info attributes attributes_count

attribute_name_index:指向CONSTANT_Utf8_info型常量索引(值固定為“Code”)坝初。
max_stack:操作數(shù)棧深度的最大值,該方法執(zhí)行的任意時刻钾军,操作數(shù)棧都不會超過這個深度鳄袍,虛擬機運行時根據(jù)此值分配棧幀中的操作棧深度。
max_locals:局部變量表所需的存儲空間吏恭。
code:存儲Java源程序編譯后生成的字節(jié)碼指令拗小。
exception_table:方法的顯式異常處理表。

(2)Exceptions屬性
Exceptions屬性用于列舉出方法中可能拋出的受查異常砸泛,即throws關(guān)鍵字后面列舉的異常十籍。

類型 名稱 數(shù)量
u2 attribute_name_index 1
u4 attribute_length 1
u2 number_of_exceptions 1
u2 exception_index_table number_of_exceptions

二、字節(jié)碼指令

字節(jié)碼指令 = 操作碼 + 操作數(shù)
操作碼:一個字節(jié)長度的唇礁、代表某種特定含義的數(shù)字勾栗;
操作數(shù):跟隨在操作碼后的零至多個參數(shù)。

操作碼只有一個字節(jié)(0-255)盏筐,所以最多只有256個操作碼围俘。

對于大部分與數(shù)據(jù)類型相關(guān)的字節(jié)碼指令,操作碼助記符都有特殊字符表示:

類型 助記符
int i
long l
short s
byte b
char c
float f
double d
reference a
  • 1. 加載和存儲指令

加載和存儲指令用于將數(shù)據(jù)在棧幀中的局部變量表和操作數(shù)棧之間來回傳輸。
(1)將一個局部變量加載到操作數(shù)棧:iload界牡、iload_<n>簿寂、lload、lload_<n>等宿亡。
(2)將一個數(shù)值從操作數(shù)棧存儲到局部變量表:istore常遂、istore_<n>等。
(3)將一個常量加載到操作數(shù)棧:bipush挽荠、sipush克胳、ldc、ldc_w等圈匆。
(4)擴充局部變量表的訪問索引:wide漠另。

注:iload_<n>是指在操作碼后直接加上了操作數(shù),省去了取操作數(shù)的動作跃赚。
例如:iload_0 等價于操作數(shù)為0時的iload指令笆搓。

  • 2. 運算指令

運算或算術(shù)指令用于對操作數(shù)棧上的兩個值進行某種特定運算,并把結(jié)果重新存入到操作數(shù)棧頂纬傲。
(1)加法指令:iadd满败、ladd、fadd嘹锁、dadd葫录。
(2)減法指令:isub、lsub领猾、fsub米同、dsub。
(3)乘法指令:imul摔竿、lmul面粮、fmul、dmul继低。
(4)除法指令:idiv熬苍、ldiv、fdiv袁翁、ddiv柴底。
(5)求余指令:irem、lrem粱胜、frem柄驻、drem。
(6)取反指令:ineg焙压、lneg鸿脓、fneg抑钟、dneg。
(7)位移指令:ishl野哭、ishr在塔、iushr、lshl拨黔、lshr蛔溃、lushr。
(8)按位或指令:ior蓉驹、lor城榛。
(9)按位與指令:iand揪利、land态兴。
(10)按位異或指令:ixor、lxor疟位。
(11)局部變量自增指令:iinc瞻润。
(12)比較指令:dcmpg、dcmpl甜刻、fcmpg绍撞、fcmpl、lcmp

  • 3. 類型轉(zhuǎn)換指令

類型轉(zhuǎn)換指令可以將兩種不同的數(shù)值類型進行互相轉(zhuǎn)換得院。
Java虛擬機直接支持寬化類型轉(zhuǎn)換傻铣,無需顯式的轉(zhuǎn)換指令:
int --> long、float祥绞、double
long --> float非洲、double
float --> double

對于窄化類型轉(zhuǎn)換,需要顯式使用轉(zhuǎn)換指令:
i2b蜕径、i2c两踏、i2s、l2i兜喻、f2i梦染、f2l、d2i朴皆、d2l帕识、d2f。

  • 4. 對象創(chuàng)建和訪問指令

(1)創(chuàng)建類實例指令:new遂铡。
(2)創(chuàng)建數(shù)組指令:newarray肮疗、anewarray、multianewarray忧便。
(3)訪問類字段和實例字段:getfield族吻、putfield帽借、getstatic、putstatic
(4)把一個數(shù)組元素加載到操作數(shù)棧的指令:baload超歌、caload砍艾、saload等。
(5)將一個操作數(shù)棧的值存儲到數(shù)組元素中:bastore巍举、castore脆荷、sastore等。
(6)取數(shù)組長度的指令:arraylength懊悯。
(7)檢查類實例類型的指令:instanceof蜓谋、checkcast。

  • 5. 操作數(shù)棧管理指令

(1)將操作數(shù)棧的棧頂一個或兩個元素出棧:pop炭分、pop2桃焕。
(2)復(fù)制棧頂一個或兩個元素并將復(fù)制值重新壓入棧頂:dup、dup2捧毛、dup_x1等观堂。
(3)將棧最頂端的兩個數(shù)值互換:swap。

  • 6. 控制轉(zhuǎn)移指令

控制轉(zhuǎn)移指令可以讓Java虛擬機有條件或無條件的去指定的指令位置處執(zhí)行程序呀忧,而不是默認的在下一條指令位置處執(zhí)行程序师痕。

概念模型上來說,控制轉(zhuǎn)移指令就是有條件或無條件的修改PC(Program Counter)寄存器的值而账。

(1)條件分支:ifeq胰坟、iflt、ifle泞辐、ifne等笔横。
(2)復(fù)合條件分支:tableswitch、lookupswitch铛碑。
(3)無條件分支:goto狠裹、goto_w、jsr汽烦、jsr_w涛菠、ret。

  • 7. 方法調(diào)用和返回指令

(1)invokevirtual:調(diào)用對象的實例方法撇吞,根據(jù)對象的實際類型進行分派俗冻。
(2)invokeinterface:調(diào)用接口方法,運行時搜索實現(xiàn)了這個接口方法的對象牍颈,找出合適的方法調(diào)用迄薄。
(3)invokespecial:調(diào)用一些需要特殊處理的實例方法,包括實例初始化方法煮岁、私有方法和父類方法讥蔽。
(4)invokestatic:調(diào)用類方法涣易。
(5)invokedynamic:在運行時動態(tài)解析出調(diào)用點限定符所引用的方法。

  • 8. 異常處理指令

Java程序中顯示拋出異常的操作(throw)都是有athrow指令實現(xiàn)的冶伞。

  • 9. 同步指令

Java虛擬機支持方法級的同步和方法內(nèi)部一段指令序列的同步新症。

方法級同步無須通過字節(jié)碼指令控制,虛擬機可以從方法表的ACC_SYNCHRONIZED訪問標志得知該方法是否為同步方法响禽,如果設(shè)置了ACC_SYNCHRONIZED標志徒爹,則執(zhí)行線程要求先成功持有管程(Monitor),管程同時只能被一個線程持有芋类,然后才能執(zhí)行方法隆嗅,方法完成(無論是正常完成還是非正常完成)后釋放管程。

同步一段指令集需要使用monitorenter和monitorexit兩條指令完成侯繁。編譯器必須確保無論這個方法是正常結(jié)束還是異常結(jié)束胖喳,調(diào)用過的每條monitorenter指令必須執(zhí)行對應(yīng)的monitorexit指令。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末巫击,一起剝皮案震驚了整個濱河市禀晓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌坝锰,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件重付,死亡現(xiàn)場離奇詭異顷级,居然都是意外死亡,警方通過查閱死者的電腦和手機确垫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門弓颈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人删掀,你說我怎么就攤上這事翔冀。” “怎么了披泪?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵纤子,是天一觀的道長。 經(jīng)常有香客問我款票,道長控硼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任艾少,我火速辦了婚禮卡乾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缚够。我一直安慰自己幔妨,他們只是感情好鹦赎,可當我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著误堡,像睡著了一般钙姊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上埂伦,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天煞额,我揣著相機與錄音,去河邊找鬼沾谜。 笑死膊毁,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的基跑。 我是一名探鬼主播婚温,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼媳否!你這毒婦竟也來了栅螟?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤篱竭,失蹤者是張志新(化名)和其女友劉穎力图,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掺逼,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡吃媒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吕喘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赘那。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖氯质,靈堂內(nèi)的尸體忽然破棺而出募舟,到底是詐尸還是另有隱情,我是刑警寧澤闻察,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布拱礁,位于F島的核電站,受9級特大地震影響蜓陌,放射性物質(zhì)發(fā)生泄漏觅彰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一钮热、第九天 我趴在偏房一處隱蔽的房頂上張望填抬。 院中可真熱鬧,春花似錦隧期、人聲如沸飒责。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宏蛉。三九已至遣臼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拾并,已是汗流浹背揍堰。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留嗅义,地道東北人屏歹。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像之碗,于是被迫代替她去往敵國和親蝙眶。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,527評論 2 349

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