注:書中第五章-調優案例分析與實戰,包含幾個案例分析和eclipse調優,但是其中的例子很簡單,就先不整理到筆記里了
目錄
一、無關性
二、Class類檔案結構
2.1 魔數與class檔案版本
2.2 常量池
javap
2.3 訪問標記
2.4 類索引、父類索引與介面索引集合
2.5 欄位表集合
2.6 方法表集合
2.7 屬性表集合
2.7.1 Code
2.7.2 Exceptions
2.7.3 LineNumberTable
2.7.4 LocalVariable Table
2.7.5 InnerClass屬性
2.7.6 Deprecated及Synthetic
2.7.7 StackMapTable
2.7.8 Signature
2.7.9 BootstrapMethods
三、位元組碼指令
3.1 位元組碼與資料型別
3.2 位元組碼用途分類
四、總結
一、無關性
實作語言無關性的基礎是虛擬機和位元組碼存盤格式,Java虛擬機不和包括Java在內的任何語言系結,它只與class檔案這種特定的二進制檔案格式所關聯,Java虛擬機不關心class的來源是何種語言,比如Groovy、Scala等語言都能產出符合規范的class檔案:Java虛擬機規范要求在class檔案中使用許多強制性的語法和結構化約束,
二、Class類檔案結構
class檔案是一組以8位bit(1位元組)為基礎單位的二進制流,各個資料專案嚴格按照順序緊湊的排列在class檔案之中,中間沒有任何分隔符,當遇到需要占用1位元組以上空間的資料項時,則會按照高位在前的方式分割成若干個1位元組進行存盤,
其中包含兩種資料型別:無符號數和表,
-
無符號數:基本的資料型別,以u1、u2、u4、u8來分別代表1個位元組、2個位元組、4個位元組和8個位元組的無符號數,無符號數可以用來描述數字、索引參考、數量值或者按照UTF-8編碼構成字串值,
-
表:由多個無符號數或者其他表作為資料項構成的復合資料型別,所有表都習慣性地以"_info"結尾,表用于描述有層次關系的復合結構的資料,整個class檔案本質上就是一張表,
class檔案主要由以下資料項構成(按順序排列)
| 型別 | 名稱 | 含義 | 數量 |
| 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.1 魔數與class檔案版本
每個class檔案的頭4個位元組稱為魔數(Magic Number),它唯一的作用是確定這個檔案是否為一個能被虛擬機接受的class檔案,固定為0xCAFEBABE,
跟著魔數的4個位元組存盤class檔案的版本號,第5、6位元組為次版本號,第7、8位元組為主版本號,Java的版本號從45開始,高版本的JDK能向下兼容低版本的class檔案,但不能運行更高版本的class檔案,即使檔案格式并沒有發生任何變化,虛擬機也必須拒絕執行超過其版本號的class檔案,
2.2 常量池
緊跟著次主版本號的是常量池入口,常量池可以理解為class檔案中的資源倉庫,每個class的常量池大小都可能不一樣,所以在常量池入口需要放置一項u2型別的資料,代表常量池容量計數器,注意,這個容量計數器是從1開始的,而不是0,也就是說,如果常量池容量為0x0016,也就是十進制的22,就代表常量池中有21項常量,索引值范圍為1~21,將第0項空出來的目的在于滿足后面某些指向常量池的索引值的資料在特定情況下需要表達“不參考任何一個常量池專案”的含義,class檔案結構中只有常量池的容量計數器是從1開始的,其它都從0開始,
常量池中主要存放兩大類常量:
- 字面量:比較接近于Java語言層面的常量概念,如文本字串、final修飾的常量值等,
- 符號參考:屬于編譯原理方面的概念,包括了類和介面的全限定名、欄位的名稱和描述符、方法的名稱和描述符,
Java代碼在編譯的時候,不像c、c++那樣有“連接”的步驟,而是在JVM加載class檔案的時候動態進行連接,在class檔案中不會保存方法、欄位的最終記憶體布局資訊,當JVM運行時,需要從常量池獲得對應的符號參考,再在類創建或運行時決議、翻譯到具體的記憶體中,
常量池中的每一項常量都是一個表,在JDK1.7之前有11種結構不同的表結構,在JDK1.7中又新增了3種,這14種表都有一個共同點:表開始的第一位是一個u1型別的標志位,代表當前這個常量屬于哪種常量型別,這14中常量型別所代表的具體含義見下表:
| 常量 | 專案(結構) | 型別 | 描述 |
| CONSTANT_Utf8_info | tag | u1 | 值為1 |
| length | u2 | UTF-8編碼字串占用的位元組數 | |
| bytes | u1 | 長度為length的UTF-8編碼字串 | |
| CONSTANT_Integer_info | tag | u1 | 值為3 |
| bytes | u4 | 按照高位在前存盤的int值 | |
| CONSTANT_Float_info | u1 | 值為4 | |
| tag | |||
| bytes | u4 | 按照高位在前存盤的float值 | |
| CONSTANT_Long_info | tag | u1 | 值為5 |
| bytes | u8 | 按照高位在前存盤的long值 | |
| CONSTANT_Double_info | tag | u1 | 值為6 |
| bytes | u8 | 按照高位在前存盤的double值 | |
| CONSTANT_Class_info | tag | u1 | 值為7 |
| index | u2 | 指向全限定名常量項的索引 | |
| CONSTANT_String_info | tag | u1 | 值為8 |
| index | u2 | 指向字串字面量的索引 | |
| CONSTANT_Fieldref_info | tag | u1 | 值為9 |
| index | u2 | 指向宣告欄位的類或介面描述符CONSTANT_Class_info的索引項 | |
| index | u2 | 指向欄位描述符CONSTANT_NameAndType的索引項 | |
| CONSTANT_Methodref_info | tag | u1 | 值為10 |
| index | u2 | 指向宣告方法的類描述符CONSTANT_Class_info的索引項 | |
| index | u2 | 指向名稱及型別描述符CONSTANT_NameAndType的索引項 | |
| CONSTANT_InterfaceMethodref_info | tag | u1 | 值為11 |
| index | u2 | 指向宣告方法的介面描述符CONSTANT_Class_info的索引項 | |
| index | u2 | 指向名稱及型別描述符CONSTANT_NameAndType的索引項 | |
| CONSTANT_NameAndType_info | tag | u1 | 值為12 |
| index | u2 | 指向該欄位或方法名稱常量項的索引 | |
| index | u2 | 指向該欄位或方法描述符常量項的索引 | |
| CONSTANT_MethodHandle_info | tag | u1 | 值為15 |
| reference_kind | u2 | 范圍[1,9],它決定了方法句柄的型別,方法句柄型別的值表示方法句柄的位元組碼行為 | |
| reference_index | u2 | 值必須是對常量池的有效索引 | |
| CONSTANT_MethodType_info | tag | u1 | 值為16 |
| descriptor_index | u2 | 值必須是對常量池的有效索引,常量池在該索引處的項必須是CONSTANT_Utf8_info結構,表示方法描述符 | |
| CONSTANT_InvokeDynamic_info | tag | u1 | 值為18 |
| bootstrap_method_attr_index | u2 | 值必須是對當前class檔案中引導方發表的bootstrap_methods[]陣列的有效索引 | |
| name_and_type_index | u2 | 值必須是對當前常量池的有效索引,常量池在該索引處的項必須是CONSTANT_NameAndType_info結構,表示方法名和方法描述符 |
有了上表的專案型別定義,我們就可以分析位元組碼了,以下面的位元組碼檔案為例:

我們跳過前8個位元組的魔數+次主版本號,獲取2個位元組的常量池大小:0x002c,即十進制的44,這代表常量池中有43項常量,然后看第一項常量的tag,值為0x0a,即十進制的10,在上表中找到tag為10的常量池專案,發現為:CONSTANT_Methodref_info,其包含兩個u2型別的index:0x0006和0x001e,即十進制的6和30,代表的也是常量池中的位置,這里中間隔了幾個常量項,就不繼續分析了,
javap
JDK專門提供了用于分析class檔案的工具:javap,使用javap分析該位元組碼(只列出了常量池部分),可以看到,和我們分析的第一項常量是一樣的:

2.3 訪問標記
常量池結束后,緊接著的兩個位元組代表訪問標記(access_flags),這個標記用于標識一些類或者介面層次的訪問資訊,包括:
| 標志名稱 | 標志值 | 含義 |
| ACC_PUBLIC | 0x0001 | 是否為public |
| ACC_FINAL | 0x0010 | 是否被宣告為final |
| ACC_SUPER | 0x0020 | 是否允許使用invokespecial位元組碼指令的新語意,該指令的語意在JDK1.2發生過改變,為了區別這條指令使用哪種語意,JDK1.0.2之后編譯的位元組碼這個標志都必須為真 |
| ACC_INTERFACE | 0x0200 | 標識是一個介面 |
| ACC_ABSTRACT | 0x0400 | 是否為abstract型別,對于介面或抽象類來說,此標志為真,其它都為假 |
| ACC_SYSTHETIC | 0x1000 | 標識這個類并非由用戶代碼產生 |
| ACC_ANNOTATION | 0x2000 | 標識是一個注解 |
| ACC_ENUM | 0x4000 | 標識是一個列舉 |
access_flags占兩個位元組,一共16位,所以有16個標志位可以使用,當前只定義了其中8個,沒有使用到的標志位要求一律為0,如果多個標志同時存在,那么使用按位或(|)運算組合,判斷標志位時,使用按位與(&),判斷結果是否大于0即可,
2.4 類索引、父類索引與介面索引集合
類索引和父類索引都是一個u2型別的資料,而介面索引是一組u2型別的資料的集合,class檔案中由這三項資料來確定類的繼承關系,
- 類索引:用于確定這個類的全限定名,指向一個型別為CONSTANT_Class_info的類描述符常量,通過該常量中的索引值可以找到定義在CONSTANT_Utf8_info型別的常量中的全限定名字串,
- 父類索引:用于確定這個類的父類的全限定名,由于Java不允許多繼承,所以父類索引只有一個,指向一個型別為CONSTANT_Class_info的類描述符常量,同類索引,
- 介面索引集合:描述這個類實作了哪些介面,這些被實作的介面將按照implements陳述句(如果類本身就是一個介面,那應當是extends陳述句)后的介面順序從左到右排列在介面索引集合中,由于介面可能存在多個,所以介面索引集合的入口是一個u2型別的介面計數器,表示介面數量,如果該類沒有實作任何介面,那么該計數器為0,如果計數器為0,那么后面介面的索引表不占用任何位元組,
2.5 欄位表集合
欄位表用于描述介面或者類中宣告的變數,欄位包括類級變數和實體級變數,但不包括在方法內部宣告的區域變數,一個欄位可以包含的資訊有:
-
欄位的作用域:public private等
-
實體變數還是類變數:static修飾符
-
可變性:final修飾符
-
并發可見性:volatile修飾符
-
可否被序列化:transient修飾符
-
欄位資料型別:基本型別、物件、陣列
-
欄位名稱
上述資訊中,各個修飾符都是布林值,要么有某個修飾符,要么沒有,很適合用標志位來表示,參考前面的訪問標記,而欄位名稱、欄位資料型別都是無法固定的,只能參考常量池中的常量來描述,欄位表結構如下:
| 名稱 | 型別 | 數量 |
| access_flags | u2 | 1 |
| name_index | u2 | 1 |
| descriptor_index | u2 | 1 |
| attribute_count | u2 | 1 |
| attributes | attribute_info | attributes_count |
其中的access_flags和前文中的類訪問標記類似,這里就不提了,跟著access_flags的name_index和descriptor_index分別代表欄位的簡單名稱以及欄位和方法的描述符,
注:
簡單名稱:指沒有型別和引數修飾的方法或欄位名稱,比如void say()方法和int i欄位的簡單名稱分別為"say"和"i",
描述符:描述符用來描述欄位的資料型別、方法的引數串列和回傳值,根據描述符規則,基本資料型別以及void都使用對應名稱的首字母大寫表示,而物件型別使用字符”L“加物件的全限定名表示,陣列的每一維度都是用一個前置的”[“字符來描述,如一個定義為"java.lang.String[][]"型別的二維陣列,將被記錄為:"[[Ljava/lang/String","int[]"被記錄為:"[I",全限定名:類似com/loren/test/MainClass,就是把類全名中的“.”替換成了”/“,為了使連續多個全限定名之間不產生混淆,使用時最后會加入一個”;“表示全限定名結束,
欄位表包含的固定資料專案到descriptor_index就結束了,不過在descriptor_index之后跟隨著一個屬性表集合用于存盤一些額外的資訊,比如對于宣告:”final static int m = 123;“那就可能會存在一項名為ConstantValue的屬性,其值指向常量123,
2.6 方法表集合
方法表的結構和前面欄位表一樣,依次包含了訪問標志(access_flags)、名稱索引(name_index)、描述符索引(descriptor_index)、屬性表集合(attributes)幾項,
相對于欄位來說,方法表的訪問標志中沒有了ACC_VOLATILE標志和ACC_TRANSIENT標志,與之相對的,新增了ACC_SYNCHRONIZED、ACC_NATIVE、ACC_STRICTFP和ACC_ABSTRACT標志,
這里要注意,方法里的Java代碼,經過編譯器編譯成位元組碼指令后,存放在方法屬性表集合中一個名為"Code"的屬性里面,
與欄位表集合相對應的,如果父類方法在子類中沒有被重寫,那么方法表集合中就不會出現來自父類的方法資訊,同樣的,有可能會出現編譯器自動添加的方法,最典型的就是類構造器"<clinit>"和實體構造器"<init>"方法,
注:在Java語言層面,方法的多載除了要與原方法有相同的簡單名稱外,還要求必須擁有一個與原方法不同的特征簽名:方法中各個引數在常量池中的欄位符號參考的集合,也就是因為回傳值不包含在特征簽名中,因此Java語言無法僅僅依靠回傳值不同來對一個已有方法進行多載,但是在class位元組碼層面,只要描述符不完全一致,兩個方法就能共存,也就是只要回傳值不同的兩個方法都能共存于一個class檔案中,
2.7 屬性表集合
前文提到過幾次,屬性表不是單獨的一部分,而是由class檔案、欄位表、方法表等攜帶,以描述某些場景專有的資訊,屬性表集合沒有那么嚴格的限制,只要不與已有屬性名重復,任何人實作的編譯器都可以向屬性表中寫入自己定義的屬性資訊,Java虛擬機運行時會忽略掉它不認識的屬性,在<<Java虛擬機規范(Java SE 7)>>版中,預定義了21項屬性,這里簡單舉幾個例子(屬性表中的屬性太多,處于篇幅考慮,這里就以Code屬性詳細說明,其他屬性就進行簡單總結):
2.7.1 Code
Code屬性出現在方法表的屬性集合中,用于存盤方法體中的代碼(位元組碼指令),但是像介面、抽象類中的方法就不存在Code屬性,Code屬性表的結構如下:
| 型別 | 名稱 | 數量 |
| 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”,代表了該屬性的名稱,attribute_length指示了屬性值的長度,由于屬性名稱索引和屬性長度一共占6個位元組,所以屬性值的長度固定為這個屬性的屬性表長度減去6個位元組,由于屬性的結構可以完全自定義,所以通過attribute_length說明屬性值占的長度即可,根據長度將屬性值讀取出來,再根據attribute_name_index確定到底該如何決議,也就是如果要跳過一個屬性,那么就是跳過2+4+attribute_length個位元組,
max_stack表示運算元堆疊的最大深度,在方法執行的任意時刻,運算元堆疊都不會超過這個深度,虛擬機運行時需要根據這個值來分配堆疊幀中的運算元堆疊深度,
max_locals代表了區域變數表所需要的存盤空間,單位是Slot,Slot是虛擬機為區域變數分配記憶體所使用的的最小單位,對于不超過32位的資料型別,占用1個Slot,對于long和double這兩種64位的資料型別需要2個Slot,另外,區域變數表中的Slot可以重用,當代碼執行超出一個區域變數的作用域時,這個變數所占的Slot可以被其他區域變數所用,
code_length和code用來存盤方法編譯后生成的位元組碼指令,code_length代表位元組碼長度,code用于存盤代表位元組碼指令的一系列u1型別的位元組流,當虛擬機讀取到code中的一個位元組碼時,就可以對應找出這個位元組碼代表的是什么指令,并且可以知道該指令后面是否需要跟隨引數,以及引數應當如何理解,u1型別可以表達256條指令,關于code_length,雖然是u4型別,但是實際只是用了2個位元組,也就是限制了一個方法不允許超過65535條位元組碼指令,如果超過這個限制,虛擬機會拒絕編譯,
注:對于實體方法,編譯器在編譯的時候,會把this關鍵字的訪問轉變為一個普通方法引數的訪問,然后在虛擬機呼叫實體方法時自動傳入此引數,因此在實體方法的區域變數表中至少會存在一個指向當前物件實體的區域變數,區域變數表中也會預留出第一個Slot位來存放物件實體的參考,也就是通過javap的結果可以看到,實體方法的Args_size>=1,Locals>=1,靜態方法則不會出現這個情況,
在位元組碼指令之后的是這個方法的顯示例外處理表,例外表包含4個欄位,描述的含義是:如果位元組碼在start_pc行到第end_pc行(不包含)之間出現了型別為catch_type或其子類的例外,則轉到第handler_pc繼續處理,可參考通過位元組碼理解try-catch-finally,
2.7.2 Exceptions
不同于Code中的例外表,這里的Exceptions屬性和Code屬性平級,作用是列舉出方法中可能拋出的受查例外,也就是方法描述時再throws關鍵字后面列舉的例外,
2.7.3 LineNumberTable
用于描述Java原始碼行號與位元組碼行號(位元組碼的偏移量)之間的對應關系,它不是運行時必須的屬性,但默認會生成到class檔案之中,可以在javac中分別使用-g:none或-g:lines選項來取消或要求生成這項資訊,如果沒有該屬性,對程式產生的主要影響就是當拋出例外時,堆疊中就不會顯示出錯的行號,并且在除錯程式的時候也無法按照原始碼行來設定斷點,
2.7.4 LocalVariable Table
用于描述堆疊幀中區域變數表中的變數與Java原始碼中定義的變數之間的關系,它不是運行時必須的屬性,但默認會生成到class檔案之中,可以在javac中分別使用-g:none或-g:vars選項來取消或要求生成這項資訊,如果沒有該屬性,也對程式運行沒有什么影響,但是當其他人參考這個方法時,所有的引數名稱都將丟失,IDE將會使用諸如arg0、arg1之類的占位符來代替原有的引數名,
2.7.5 InnerClass屬性
用與記錄內部類和宿主類之間的關聯,如果一個類中定義了內部類,那編譯器將會為它以及它所包含的內部類生成InnerClasses屬性,
2.7.6 Deprecated及Synthetic
兩個都屬于標志型別的布爾屬性,只存在有和沒有的區別,沒有屬性值的概念,Deprecated屬性用于標識某個類、欄位或方法,已經被程式推薦不再使用,Synthetic代表此欄位或者方法并不是由Java原始碼直接產生的,而是由編譯器自行添加的,
2.7.7 StackMapTable
包含0至多個堆疊映射幀,每個堆疊映射幀都顯式或隱式地代表了一個位元組碼偏移量,用于表示該執行到該位元組碼時,區域變數表和運算元堆疊的驗證型別,型別檢查驗證器會通過檢查目標方法的區域變數和運算元堆疊所需要的型別來確定一段位元組碼指令是否符合邏輯約束,
2.7.8 Signature
由于JAVA語言的泛型采用的擦除法實作的偽泛型,由于泛型擦除,原有的泛型型別會被替換為泛型上限(如果沒有指定上限,則為Object),所以在運行期無法通過反射獲得真實的泛型資訊,Signature屬性就是為了彌補這個缺陷而增設的,現在Java的反射API能夠獲取泛型型別,最終的資料來源也就是這個屬性,它可以出現于類、欄位表、方法表結構的屬性表中,
2.7.9 BootstrapMethods
位于類檔案的屬性表中,用于保存invokedynamic指令參考的引導方法限定符,
三、位元組碼指令
Java虛擬機的指令由一個位元組長度的、代表著某種特定操作含義的數字(稱為操作碼,Opcode)以及跟隨其后的0至多個所需引數(稱為運算元,Operands)構成,由于Java虛擬機采用面向運算元堆疊的架構,而不是暫存器,所以多大數的指令都不包含運算元,只有一個操作碼(追求小數量、高傳輸效率),對運算元堆疊進行出堆疊、入堆疊操作,由于操作碼的長度為一個位元組,所以指令集的操作碼總數不能超過256條,
3.1 位元組碼與資料型別
大多數的指令都包含了其操作所對應的資料型別資訊,比如,iload指定用于從區域變數表加載int型資料到運算元堆疊,而fload指令加載的則是float型別的資料,但是前面提到操作碼只有一個位元組,如果每一種與資料型別相關的指令都支持Java虛擬機所有運行時資料型別的話,那指令的數量絕對會超出一個位元組所能表示的數字范圍,
因此Java虛擬機的指令集對于特定的操作只提供了有限的型別相關指令去支持,例如load指令有操作int型別的iload,但是沒有操作byte型別的同類指令,大部分的指令都沒有支持整數型別byte、char和short,甚至沒有任何指令支持boolean型別,編譯器會在編譯期或運行期將byte和short型別的資料帶符號擴展為相應的int型別資料,將boolean和char型別的資料零位擴展為相應的int型別資料,與之類似,在處理boolean、byte、short和char型別的陣列時,也會轉換為使用對應的int型別的位元組碼指令處理,因此大多數對于boolean、byte、short、char型別資料的操作,實際上都是使用相應的int型別作為運算型別,
3.2 位元組碼用途分類
-
加載和存盤指令:用于將資料在堆疊幀中的區域變數表和運算元堆疊之間來回傳輸,比如iload、istore、bipush等,
-
運算指令:用于對兩個運算元堆疊上的值進行某種特定運算,并把結果重新存入到運算元堆疊頂,比如加法指令:iadd,減法指令:isub等等,
-
型別轉換指令:將兩種不同的數值型別進行相互轉換,這些轉換操作一般用于實作用戶代碼中的顯示型別轉換操作,或者處理前面提到的指令集中資料型別相關指令無法與資料型別一一對應的問題(byte、short等擴展為int),
-
物件創建與訪問指令:要注意Java虛擬機對類實體和陣列的創建與操作使用了不同的位元組碼指令,創建類實體:new,創建陣列:nwarray、anewarray等,
-
運算元堆疊管理指令:類似于操作普通資料結構中的堆疊,Java虛擬機提供了一些用于直接操作運算元堆疊的指令,比如pop、dup、swap等,
-
控制轉移指令:可以讓Java虛擬機有條件或無條件的修改程式計數器的值,包括條件分支(比如ifeq)、復合條件分支(比如tableswitch)、無條件分支(比如goto)等等,
-
方法呼叫和回傳指令:方法呼叫指令包括,像invokevirtual指令:用于呼叫物件的實體方法,invokespecial指令:呼叫一些需要特殊處理的方法,包括實體初始化方法、私有方法和父類方法;方法呼叫指令與資料型別無關,但方法回傳指令是根據回傳值型別區分的,包括ireturn(回傳boolean、byte、char、short、int),lreturn、freturn、dreturn和areturn,另外還有一條return指令供宣告為void的方法、實體初始化方法以及類和介面類初始化方法使用,
-
例外處理指令:Java程式中顯示拋出例外的操作(throw)都是用athrow指令來實作,除此之外,Java虛擬機規范還規定了許多運行時例外會在其他Java虛擬機指令檢測到例外狀況時自動拋出,比如在整數運算中,當除數為0時,虛擬機會在idiv或ldiv指令中拋出ArithmeticException例外,現在在Java虛擬機中處理例外是采用例外表完成的,以前則使用的是jsr和ret指令實作,
-
同步指令:synchronized陳述句塊對應的指令就是monitorenter和monitorexit,編譯器必須確保無論方法通過何種方式完成,方法中呼叫過的每條monitorenter指令都必須執行其對應的monitorexit指令,所以為了保證在方法例外完成時,monitorenter和monitorexit指令依然可以正確配對執行,編譯器會自動產生一個例外處理器,這個例外處理器宣告可以處理所有的例外,關于synchronized的深入理解,可參考Java并發編程(九):深入JVM 核心全面理解 Synchronized,
四、總結
理解位元組碼檔案結構和分析位元組碼指令是每一個Java程式員都應該掌握的基礎技能,本章主要介紹了class檔案的結構組成、class資料的存盤和訪問、位元組碼指令的一些描述,ava專案安全發布--Jar包(class)加解密實踐中應用式的操作了位元組碼,感興趣的可以瞅瞅~~
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/265108.html
標籤:java
下一篇:設計模式:簡單工廠模式
