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指令。