一瘦馍、概述
- Java字節(jié)碼對于虛擬機,就好像匯編語言對于計算機腌零,屬于基本執(zhí)行指令
- Java虛擬機的指令由“一個字節(jié)長度”的梯找、代表著某種特定操作含義的數(shù)字(稱為 操作碼,Opcode)以及跟隨其后的零至多個代表此操作所需參數(shù)(稱為操作數(shù)益涧,Operands)而構(gòu)成锈锤。由于Java虛擬機采用面向操作數(shù)棧而不是寄存器的結(jié)構(gòu),所以大多數(shù)的指令都不包含操作數(shù)闲询,只有一個操作碼
- 由于限制了 Java 虛擬機操作碼的長度為一個字節(jié)(0~255)久免,這意味著指令集的操作碼總數(shù)不可能超過 256 條
- 熟悉虛擬機的指令對于動態(tài)字節(jié)碼生成、反編譯 Class 文件扭弧、Class文件修補都有著非常重要的價值阎姥。因此閱讀字節(jié)碼作為了解 Java 虛擬機的基礎(chǔ)技能,需要熟練掌握常見指令
- 如果不考慮異常處理的話鸽捻,那么Java虛擬機的解釋器可以使用下面這個偽代碼當(dāng)做最基本的執(zhí)行模型來理解
do {
自動計算PC寄存器的值加1呼巴;
根據(jù)PC寄存器的指示位置,從字節(jié)碼流中取出操作碼御蒲;
if(字節(jié)碼存在操作數(shù))從字節(jié)碼流中取出操作數(shù)衣赶;
執(zhí)行操作碼所定義的操作;
}while(字節(jié)碼長度 > 0)
二厚满、字節(jié)碼與數(shù)據(jù)類型
在Java虛擬機的指令集中府瞄,大多數(shù)的指令都包含了其操作所對應(yīng)的數(shù)據(jù)類型信息。如 iload 指令用于從局部變量表中加載 int 類型的數(shù)據(jù)到“操作數(shù)椀夤浚”中遵馆,而fload指令加載的則是 float 類型的數(shù)據(jù)
1鲸郊、對于大部分與數(shù)據(jù)類型相關(guān)的字節(jié)碼指令,它們的操作碼助記符中都有特殊的字符來表名專門為哪種數(shù)據(jù)類型服務(wù)
- i:代表 int 類型的數(shù)據(jù)操作
- l:代表 long 類型的數(shù)據(jù)操作
- s:代表 short 類型的數(shù)據(jù)操作
- b:代表 byte 類型的數(shù)據(jù)操作
- c:代表 char 類型的數(shù)據(jù)操作
- f:代表 float 類型的數(shù)據(jù)操作
- d:代表 double 類型的數(shù)據(jù)操作
- 也有一些指令的助記符中沒有明確指明操作類型的字母(arraylength指令)货邓,它沒有代表數(shù)據(jù)類型的特殊字符秆撮,但是操作數(shù)永遠只能是一個數(shù)組類型的對象
- byte、char逻恐、short像吻、boolean類型數(shù)據(jù)轉(zhuǎn)為 int 類型數(shù)據(jù)
2、指令分類
- 加載與存儲指令
- 算術(shù)指令
- 類型轉(zhuǎn)換指令
- 對象的創(chuàng)建與訪問指令
- 方法調(diào)用與返回指令
- 操作數(shù)棧管理執(zhí)行
- 比較控制指令
- 異常處理指令
- 同步控制指令
3复隆、做值相關(guān)操作時
- 一個指令拨匆,可以從局部變量表、常量池挽拂、堆中對象惭每、方法調(diào)用、系統(tǒng)調(diào)用中等取得數(shù)據(jù)亏栈,這些數(shù)據(jù)(可能是值台腥、可能是對象的引用)被壓入操作數(shù)棧
- 一個指令,也可以從操作數(shù)棧中取出一到多個值(pop多次)绒北,完成賦值黎侈、加減乘除、方法傳參闷游、系統(tǒng)調(diào)用等等操作
三峻汉、加載與存儲指令
1、作用
- 加載和存儲指令用于將數(shù)據(jù)從棧幀的局部變量表和操作數(shù)棧之間來回傳遞
2脐往、常用指令
- 局部變量壓棧指令:將一個局部變量加載到操作數(shù)棧:xload休吠、xload_<n>(其中x為 i、l业簿、f瘤礁、d、a梅尤;n為 0~3)
- 常量入棧指令:將一個常量加載到操作數(shù)棧:bipush柜思、sipush、ldc巷燥、ldc_w酝蜒、ldc2_w、aconst_null矾湃、iconst_m1、iconst_<i>堕澄、lconst_<l>邀跃、fconst_<f>霉咨、dconst_<d>
- 出棧裝入局部變量表指令:將一個數(shù)值從操作數(shù)棧存儲到局部變量表:xstore、xstore_<n>(其中x為i拍屑、l途戒、f、d僵驰、a喷斋;n為0~3);xastore(其中x為i蒜茴、l星爪、f、d粉私、a顽腾、b、c诺核、s)
- 擴充局部變量表的訪問索引的指令:wide
3抄肖、說明
- 有一部分指令助記符是以尖括號結(jié)尾的(iload_<n>)。這些指令助記符實際上代表了一組指令(如:iload_<n>代表了iload_0窖杀、iload_1漓摩、iload_2和iload_3這幾個指令)。這幾組指令都是某個帶有一個操作數(shù)的通用指令(如iload)的特殊形式入客,對于這若干組特殊指令來說管毙,它們表面上沒有操作數(shù),不需要進行取操作數(shù)的動作痊项,但操作數(shù)都隱含在指令中
- iload_0的語義與操作數(shù)為0時的 iload 指令語義完全一致锅风。在尖括號之間的字母指定了指令隱含操作數(shù)的數(shù)據(jù)類型
- <n>:代表非負的整數(shù)
- <i>:代表是 int 類型數(shù)據(jù)
- <l>:代表long類型
- <f>:代表float類型
- <d>:代表double類型
- 操作 byte、char鞍泉、short和boolean類型數(shù)據(jù)時皱埠,經(jīng)常用int類型的指令來表示
- iload_0:代表將局部變量表中索引為0位置的數(shù)據(jù)壓入操作數(shù)棧中
- iload 0:代表將局部變量表中索引為0位置的數(shù)據(jù)壓入操作數(shù)棧中
- iload 4:代表將局部變量表中索引為4位置的數(shù)據(jù)壓入操作數(shù)棧中
4、操作數(shù)棧(Operand Stack)
-
Java字節(jié)碼是Java虛擬機所使用的指令集咖驮。因此边器,它與Java虛擬機基于棧的計算模型是密不可分的。在解釋執(zhí)行過程中托修,每當(dāng)為Java方法分配棧幀時忘巧,Java虛擬機往往需要開辟一塊額外的空間作為“操作數(shù)棧,來存放計算的操作數(shù)以及返回結(jié)果”
5睦刃、局部變量表(Local Variables)
- Java方法棧幀的另外一個重要組成部分則是局部變量區(qū)砚嘴,字節(jié)碼程序可以將計算的結(jié)果緩存在局部變量區(qū)之中。實際上,Java虛擬機將局部變量區(qū)當(dāng)成一個數(shù)組际长,依次存放 this 指針(僅非靜態(tài)方法)耸采,所傳入的參數(shù),以及字節(jié)碼中的局部變量
-
long類型以及double類型的值將占據(jù)兩個單元工育,其余類型僅占據(jù)一個單元
- 在棧幀中虾宇,與性能調(diào)優(yōu)關(guān)系最為密切的部分就是局部變量表。局部變量表中的變量也是重要的垃圾回收根節(jié)點如绸,只要被局部變量表中直接或間接引用的對象都不會被回收
- 代碼
public class LoadAndStoreTest {
public void load(int num, Object obj, long count, boolean flag, short[] arr) {
System.out.println("num = " + num);
System.out.println("obj = " + obj);
System.out.println("count = " + count);
System.out.println("flag = " + flag);
System.out.println("arr = " + arr);
}
}
- 字節(jié)碼
0 getstatic #2 <java/lang/System.out>
3 new #3 <java/lang/StringBuilder>
6 dup
7 invokespecial #4 <java/lang/StringBuilder.<init>>
10 ldc #5 <num = >
12 invokevirtual #6 <java/lang/StringBuilder.append>
15 iload_1
16 invokevirtual #7 <java/lang/StringBuilder.append>
19 invokevirtual #8 <java/lang/StringBuilder.toString>
22 invokevirtual #9 <java/io/PrintStream.println>
25 getstatic #2 <java/lang/System.out>
28 new #3 <java/lang/StringBuilder>
31 dup
32 invokespecial #4 <java/lang/StringBuilder.<init>>
35 ldc #10 <obj = >
37 invokevirtual #6 <java/lang/StringBuilder.append>
40 aload_2
41 invokevirtual #11 <java/lang/StringBuilder.append>
44 invokevirtual #8 <java/lang/StringBuilder.toString>
47 invokevirtual #9 <java/io/PrintStream.println>
50 getstatic #2 <java/lang/System.out>
53 new #3 <java/lang/StringBuilder>
56 dup
57 invokespecial #4 <java/lang/StringBuilder.<init>>
60 ldc #12 <count = >
62 invokevirtual #6 <java/lang/StringBuilder.append>
65 lload_3
66 invokevirtual #13 <java/lang/StringBuilder.append>
69 invokevirtual #8 <java/lang/StringBuilder.toString>
72 invokevirtual #9 <java/io/PrintStream.println>
75 getstatic #2 <java/lang/System.out>
78 new #3 <java/lang/StringBuilder>
81 dup
82 invokespecial #4 <java/lang/StringBuilder.<init>>
85 ldc #14 <flag = >
87 invokevirtual #6 <java/lang/StringBuilder.append>
90 iload 5
92 invokevirtual #15 <java/lang/StringBuilder.append>
95 invokevirtual #8 <java/lang/StringBuilder.toString>
98 invokevirtual #9 <java/io/PrintStream.println>
101 getstatic #2 <java/lang/System.out>
104 new #3 <java/lang/StringBuilder>
107 dup
108 invokespecial #4 <java/lang/StringBuilder.<init>>
111 ldc #16 <arr = >
113 invokevirtual #6 <java/lang/StringBuilder.append>
116 aload 6
118 invokevirtual #11 <java/lang/StringBuilder.append>
121 invokevirtual #8 <java/lang/StringBuilder.toString>
124 invokevirtual #9 <java/io/PrintStream.println>
127 return
-
LocalVariableTable
1嘱朽、常量入棧指令
- 常量入棧指令的功能是將常數(shù)壓入操作數(shù)棧,根據(jù)數(shù)據(jù)類型和入棧內(nèi)容的不同怔接,又可以分為const系列搪泳、push系列和ldc指令
1、指令const系統(tǒng)
- 用于對特定的常量入棧蜕提,入棧的常量隱含在指令本身里
- iconst_<i>(i從-15)(如:iconst_m1將-1壓入操作數(shù)棧;iconst_x(x為05)將x壓入棧)
- lconst_<l>(l從0~1)(如:lconst_0森书、lconst_1分別將長整型0和1壓入棧)
- fconst_<f>(f從0~2)(如:fconst_0、fconst_1谎势、fconst_2分別將浮點數(shù)0凛膏、1、2壓入棧)
- dconst_<d>(d從0~1)(dconst_0和dconst_1分別將double型的0和1壓入棧)
- aconst_null(將 null 壓入操作數(shù)棧)
- 指令助記符的第一個字符表示數(shù)據(jù)類型
- i:表示整數(shù)
- l:表示長整數(shù)
- f:表示浮點數(shù)
- d:表示雙精度浮點
- a:表示對象引用
- 指令隱含操作的參數(shù)會以下劃線形式給出
2脏榆、指令push系統(tǒng)
- 主要包括 bipush 和 sipush
- bipush:接收8位整數(shù)作為參數(shù)
- sipush:接收16位整數(shù)作為參數(shù)
3猖毫、指令ldc系統(tǒng)
- 如果以上指令都不能滿足需求,那么可以使用萬能的ldc指令须喂,它可以接受一個 8 位的參數(shù)吁断,該參數(shù)指向常量池中的int、float或者String的索引坞生,將指定的內(nèi)容壓入堆棧
4仔役、指令ldc_w系統(tǒng)
- 類似于ldc,它接收兩個8位參數(shù)是己,能支持的索引范圍大于ldc又兵。如果要壓入的元素是long或者double類型的,則使用ldc2_指令
5卒废、總結(jié)
- 代碼
public class LoadAndStoreTest {
// 2沛厨、常量入棧指令
public void pushConstLdc() {
int i = -1;
int a = 5;
int b = 6;
int c = 127;
int d = 128;
int e = 32767;
int f = 32768;
}
}
- 字節(jié)碼
0 iconst_m1
1 istore_1
2 iconst_5
3 istore_2
4 bipush 6
6 istore_3
7 bipush 127
9 istore 4
11 sipush 128
14 istore 5
16 sipush 32767
19 istore 6
21 ldc #2 <32768>
23 istore 7
25 return
- 小結(jié)
- 1、常量-1使用iconst_m1表示
- 2摔认、常量6無法使用iconst_表示范圍逆皮,所以使用bipush 6來存儲
- 3、bipush可以表示的最大8位數(shù)為127(bipush 127)参袱,大于127的使用接收16位參數(shù)的sipush(sipush 128)
- 4电谣、sipush接收的16位參數(shù)最大值為32767(sipush 32767)秽梅,超出的使用ldc
2、出棧轉(zhuǎn)入局部變量表指令
出棧裝入局部變量表指令用于將操作數(shù)棧中棧頂元素彈出后辰企,裝入局部變量表的指定位置硫椰,用于給局部變量賦值
-
這里指令主要以store的形式存在椰苟,比如xstore(x為i、l拍嵌、f镐捧、d潜索、a)、xstore_n(x為i懂酱、l竹习、f、d列牺、a整陌,n為0~3)
- 其中,指令istore_n將從操作數(shù)棧中彈出一個整數(shù)瞎领,并把它賦值給局部變量索引n位置
- 指令xstore由于沒有隱含參數(shù)信息泌辫,故需要提供一個byte類型的參數(shù)類指定目標(biāo)局部變量表的位置
一般來說,類似像store這樣的命令需要帶一個參數(shù)九默,用來指明將彈出的元素放在局部變量表的第幾個位置震放。但是為了盡可能壓縮指令大小,使用專門的istore_1指令表示將彈出的元素放置在局部變量表第1個位置驼修。類似的還有istore_0殿遂、istore_1、istore_2乙各、istore_3墨礁,它們分別表示從操作數(shù)棧頂彈出一個元素,存放在局部變量表第0耳峦、1恩静、2、3個位置妇萄。由于局部變量表前幾個位置總是非常常用蜕企,因此這種做法雖然增加了指令數(shù)量,但是可以大大壓縮生成的字節(jié)碼的體積冠句。如果需要存儲的槽位大于3轻掩,那么可以使用istore指令,外加一個參數(shù)懦底,用來表示需要存放的槽位位置
四唇牧、算術(shù)指令
1罕扎、運算時的溢出:數(shù)據(jù)運算可能會導(dǎo)致溢出,例如兩個很大的正整數(shù)相加丐重,結(jié)果可能是一個負數(shù)腔召。Java虛擬機規(guī)范并無明確規(guī)定過整型數(shù)據(jù)溢出的具體結(jié)果,僅規(guī)定了在處理整型數(shù)據(jù)時扮惦,只有除法指令以及求余指令中當(dāng)出現(xiàn)除數(shù)為0時會導(dǎo)致虛擬機拋出異常 ArithmeticException
- code
@Test
public void method1() {
int i = 0;
int j = i / 0;
System.out.println(j);
}
- 輸出
java.lang.ArithmeticException: / by zero
- code
@Test
public void method2() {
int i = 10;
double j = i / 0.0;
System.out.println(j);
}
- 輸出
Infinity
- code
@Test
public void method3() {
int i = 0;
double j = i / 0.0;
System.out.println(j);
}
- 輸出
NaN
2臀蛛、運算模式
- 向最接近數(shù)舍如模式(四舍五入):JVM要求在進行浮點數(shù)計算時,所有的運算結(jié)果都必須舍入到適當(dāng)?shù)木妊旅郏蔷_結(jié)果必須舍入為可被標(biāo)識的最接近的精確值浊仆,如果有兩種可表示的形式與該值一樣接近,將優(yōu)先選擇最低有效位為零的豫领。
- 向零舍入模式(取整):將浮點數(shù)轉(zhuǎn)換為整數(shù)時抡柿,采用該模式,該模式將在目標(biāo)數(shù)值類型中選擇一個最接近但是不大于原值的數(shù)字作為最精確的舍入結(jié)果
3等恐、NaN值使用:當(dāng)一個操作產(chǎn)生溢出時洲劣,將會使用有符號的無窮大表示,如果某個操作結(jié)果沒有明確的數(shù)學(xué)定義的話课蔬,將會使用NaN值來表示囱稽。而且所有使用NaN值作為操作數(shù)的算術(shù)操作,結(jié)果都會返回 NaN
4购笆、所有算術(shù)指令
- 加法指令:iadd粗悯、ladd、fadd同欠、dadd
- 減法指令:isub样傍、lsub、fsub铺遂、dsub
- 乘法指令:imul衫哥、lmul、fmul襟锐、dmul
- 除法指令:idiv撤逢、ldiv、fdiv粮坞、ddiv
- 求余指令:irem蚊荣、lrem、frem莫杈、drem
- 取反指令:ineg互例、lneg、fneg筝闹、dneg
- 自增指令:iinc
- 位運算指令:
- 位移指令:ishl媳叨、ishr腥光、iushr、lshl糊秆、lshr武福、lushr
- 按位或指令:ior、lor
- 按位與指令:iand痘番、land
- 按位異或指令:ixor捉片、lxor
- 比較指令:dcmpg、dcmpl夫偶、fcmpg界睁、fcmpl、lcmp
實例
1兵拢、實例1:float 取反指令
- 代碼
@Test
public void method4() {
float i = 10;
float j = -i;
i = -j;
}
- 字節(jié)碼
0 ldc #2 <10.0>
2 fstore_1
3 fload_1
4 fneg
5 fstore_2
6 fload_2
7 fneg
8 fstore_1
9 return
代碼執(zhí)行流程
-
0 ldc #2 <10.0>:把float類型的常量10.0存入“操作數(shù)棧”
-
2 fstore_1:操作數(shù)出棧存入到局部變量表下標(biāo)為1的位置
-
3 fload_1:加載局部變量表中數(shù)據(jù)到操作數(shù)棧
-
4 fneg:取出操作數(shù)棧棧頂元素取反
-
5 fstore_2:操作數(shù)棧棧頂數(shù)據(jù)存儲到局部變量表下標(biāo)為2的位置上
-
6 fload_2:加載局部變量表位置2的數(shù)據(jù)到操作數(shù)棧中
-
7 fneg:取出操作數(shù)棧棧頂元素取反
-
8 fstore_1:取出操作數(shù)棧棧頂元素存儲到局部變量表下標(biāo)為1的位置
2逾礁、實例2:i = i + 10 與 i += 10说铃;
- 代碼
@Test
public void method5() {
int i = 100;
i = i + 10;
}
- 字節(jié)碼
0 bipush 100
2 istore_1
3 iload_1
4 bipush 10
6 iadd
7 istore_1
8 return
-
0 bipush 100:把100放入操作數(shù)棧
-
2 istore_1:把操作數(shù)棧棧頂元素數(shù)據(jù)存入局部變量表下標(biāo)為1的位置
-
3 iload_1:加載局部變量表下標(biāo)1中的數(shù)據(jù)放入到操作數(shù)棧
-
4 bipush 10:把常量10放入操作數(shù)棧
-
6 iadd:取出操作數(shù)棧前2位數(shù)據(jù)相加后再放入操作數(shù)棧
-
7 istore_1:把操作數(shù)棧頂元素存儲到局部變量表下標(biāo)為1的位置
:i += 10;
- 代碼
public void method5() {
int i = 100;
i += 10;
}
- 字節(jié)碼
0 bipush 100
2 istore_1
3 iinc 1 by 10
6 return
-
0 bipush 100:把100放入操作數(shù)棧
-
2 istore_1:把操作數(shù)棧棧頂元素數(shù)據(jù)存入局部變量表下標(biāo)為1的位置
-
3 iinc 1 by 10:局部變量下標(biāo)為1的數(shù)據(jù)加10
3嘹履、關(guān)于i++ 和 ++i
3.1腻扇、分析++i
- 代碼
/**
* 關(guān)于(前)++i 和 (后)i++
*/
public void method6() {
int i = 10;
++i;
}
- 字節(jié)碼
0 bipush 10
2 istore_1
3 iinc 1 by 1
6 return
-
0 bipush 10:常量10入操作數(shù)棧
-
2 istore_1:操作數(shù)棧棧頂數(shù)據(jù)存放到局部變量表下標(biāo)為1的位置
-
3 iinc 1 by 1:局部變量表下標(biāo)1的數(shù)據(jù)+1
3.2、i++字節(jié)碼分析結(jié)果與++i一樣
實例4 int a = i++;
- 代碼
public void method7() {
int i = 10;
int a = i++;
}
- 字節(jié)碼
0 bipush 10
2 istore_1
3 iload_1
4 iinc 1 by 1
7 istore_2
8 return
-
0 bipush 10:把常量10加載到操作數(shù)棧
-
2 istore_1:把操作數(shù)棧頂數(shù)據(jù)存儲到局部變量表下標(biāo)為1的位置上
-
3 iload_1:加載局部變量表下標(biāo)為1的數(shù)據(jù)到操作數(shù)棧中
-
4 iinc 1 by 1:局部變量表下標(biāo)為1的數(shù)據(jù)加1
-
7 istore_2:將操作數(shù)棧頂數(shù)據(jù)存入到局部變量表下標(biāo)為2的位置
實例5:int a = ++i
- 代碼
public void method8() {
int i = 10;
int a = ++i;
}
- 字節(jié)碼
0 bipush 10
2 istore_1
3 iinc 1 by 1
6 iload_1
7 istore_2
8 return
-
0 bipush 10:常量10載入操作數(shù)棧
-
2 istore_1:將操作數(shù)棧頂數(shù)據(jù)存儲到局部變量表下標(biāo)為1的位置中
-
3 iinc 1 by 1:局部變量表下標(biāo)為1的數(shù)據(jù)+1
-
6 iload_1:加載局部變量表下標(biāo)1中數(shù)據(jù)到操作數(shù)棧中
-
7 istore_2:將操作數(shù)棧中的數(shù)據(jù)存儲到局部變量表下標(biāo)為2的位置中
4砾嫉、比較指令
- 比較指令的作用是比較兩個元素的大小幼苛,并將比較結(jié)果入棧
- 比較指令:
- dcmpg
- dcmpl
- fcmpg
- fcmpl
- lcmp
- 首字符d表示double類型,f表示float類型焕刮,l表示long類型
- 對于double和float類型的數(shù)字舶沿,由于NaN的存在,各有兩個版本的比較指令配并,兩個版本的指令針對NaN值括荡,處理結(jié)果不同
- 指令fcmpg和fcmpl都從棧中彈出兩個操作數(shù),并將它們做比較溉旋,設(shè)棧頂?shù)脑貫関2畸冲,第2位的元素為v1,如果v1=v2观腊,則壓入0邑闲;若v1>v2則壓入1;若v1<v2則壓入-1
- 如果遇到NaN值梧油,fcmpg會壓入1苫耸,而fcmpl會壓入-1
五、類型轉(zhuǎn)換指令
1類型轉(zhuǎn)換指令說明
- 類型轉(zhuǎn)換指令可以將兩種不同的數(shù)組類型進行相互轉(zhuǎn)換
- 這些轉(zhuǎn)換操作一般用于實現(xiàn)用戶代碼中的“顯式類型轉(zhuǎn)換操作”婶溯,或者用來處理“字節(jié)碼指令集中數(shù)據(jù)類型相關(guān)指令”無法與“數(shù)據(jù)類型”一一對應(yīng)的問題
2鲸阔、寬化類型轉(zhuǎn)換(Widening Numeric Conversions)
2.1偷霉、轉(zhuǎn)換規(guī)則
- Java虛擬機直接支持以下數(shù)值的寬化類型轉(zhuǎn)換(小范圍類型向大范圍類型的安全轉(zhuǎn)換)。也就是褐筛,并不需要指令執(zhí)行类少。
- 從int類型-->long、float或者double類型渔扎。對應(yīng)指令為:i2l硫狞、i2f、i2d
- 從long類型-->float晃痴、double類型残吩。對應(yīng)指令為:l2f、l2d
- 從float類型-->double類型倘核。對應(yīng)指令為:f2d
- 小結(jié):int ---> long ---> float ---> double
2.2泣侮、精度損失問題
寬化類型轉(zhuǎn)換是不會因為超過目標(biāo)類型最大值而丟失信息的。例如:從 int 類型到 long 紧唱,或者從int類型到double活尊,都不會丟失任何信息,轉(zhuǎn)換前后的值是精度相等的
從 int漏益、long類型數(shù)據(jù)轉(zhuǎn)換到 float蛹锰,或者 long 類型數(shù)值轉(zhuǎn)換到 double 時,將可能發(fā)生精度丟失——可能丟失掉幾個最低有效為上的值绰疤,轉(zhuǎn)換后的浮點數(shù)值是根據(jù)IEEE754最接近舍入模式所得到的正確整數(shù)值
盡管“寬化類型轉(zhuǎn)換”實際上是可能發(fā)生精度丟失的铜犬,但是這種轉(zhuǎn)換永遠不會導(dǎo)致Java虛擬機拋出運行時異常
-
從byte、char和short類型到int類型的“寬化類型轉(zhuǎn)換”實際上是不存在的轻庆。對于byte類型轉(zhuǎn)為int癣猾,虛擬機并沒有做實質(zhì)性的轉(zhuǎn)化處理,只是簡單地通過操作數(shù)棧交換了兩個數(shù)據(jù)榨了。而將byte轉(zhuǎn)為long時煎谍,使用的是i2l,可以看到在內(nèi)部byte在這里已經(jīng)等同于int類型處理龙屉,類似的還有short類型呐粘,這種處理方式有兩個特點:
- 特點1:一方面可以減少實際的數(shù)據(jù)類型,如果為short和byte都準(zhǔn)備一套指令转捕,那么指令的數(shù)量就會大增作岖,而“虛擬機目前的設(shè)計上,只愿意使用一個字節(jié)表示指令五芝,因此指令總數(shù)不能超過256個痘儡,為了節(jié)省指令資源,將short和byte當(dāng)做int處理也是情理之中”
- 特點2:另一方面枢步,由于局部變量表中槽位固定為32位沉删,無論是byte或者short存入局部變量表渐尿,都會占用32位空間。從這個角度來看矾瑰,也沒有必要特意區(qū)分這幾種數(shù)據(jù)類型
2.3砖茸、實例測試
- 代碼
package com.lkty.loadandstore;
/**
* 類型轉(zhuǎn)換指令
*/
public class ClassCastTest {
/**
* 寬化類型轉(zhuǎn)換
*/
public void upCast1() {
int i = 10;
long l = i;
float f = i;
double d = i;
float f1 = l;
double d1 = l;
double d2 = f1;
}
}
- 字節(jié)碼
0 bipush 10
2 istore_1
3 iload_1
4 i2l
5 lstore_2
6 iload_1
7 i2f
8 fstore 4
10 iload_1
11 i2d
12 dstore 5
14 lload_2
15 l2f
16 fstore 7
18 lload_2
19 l2d
20 dstore 8
22 fload 7
24 f2d
25 dstore 10
27 return
-
0 bipush 10:將常量10加載到操作數(shù)棧中
-
2 istore_1:將操作數(shù)棧中棧頂數(shù)據(jù)存儲到局部變量表下標(biāo)為1的位置
-
3 iload_1:將局部變量表下標(biāo)為1中的數(shù)據(jù)加載到操作數(shù)棧中
4 i2l:將int類型數(shù)據(jù)轉(zhuǎn)換為long類型數(shù)據(jù)
-
5 lstore_2:將操作數(shù)棧中數(shù)據(jù)存儲到局部變量表下標(biāo)為2的位置
-
6 iload_1:將局部變量表下標(biāo)為1中的數(shù)據(jù)加載到操作數(shù)棧中
7 i2f:將int類型數(shù)據(jù)轉(zhuǎn)為float類型數(shù)據(jù)
-
8 fstore 4:將操作數(shù)棧中棧頂?shù)膄loat類型數(shù)據(jù)存儲到局部變量表下標(biāo)為4的位置
-
10 iload_1:將局部變量表下標(biāo)為1中的數(shù)據(jù)加載到操作數(shù)棧中
11 i2d:將int類型數(shù)據(jù)轉(zhuǎn)換為double類型數(shù)據(jù)
-
12 dstore 5:將操作數(shù)棧中棧頂?shù)膁ouble類型數(shù)據(jù)存儲到局部變量表下標(biāo)為5的位置上
-
14 lload_2:將局部變量表下標(biāo)為2中的long類型數(shù)據(jù)加載到操作數(shù)棧中
15 l2f:將long類型數(shù)據(jù)轉(zhuǎn)換為float類型數(shù)據(jù)
-
16 fstore 7:將操作數(shù)棧中棧頂?shù)膄loat類型數(shù)據(jù)存儲到局部變量表下標(biāo)為7的位置
-
18 lload_2:將局部變量表下標(biāo)為2中的long類型數(shù)據(jù)加載到操作數(shù)棧中
19 l2d:將long類型的數(shù)據(jù)轉(zhuǎn)換為double類型數(shù)據(jù)
-
20 dstore 8:將操作數(shù)棧中棧頂?shù)膁ouble類型數(shù)據(jù)存儲到局部變量表下標(biāo)為8的位置
-
22 fload 7:將局部變量表下標(biāo)為7中的float類型數(shù)據(jù)加載到操作數(shù)棧中
24 f2d:將float類型數(shù)據(jù)轉(zhuǎn)換為double類型數(shù)據(jù)
-
25 dstore 10:將操作數(shù)棧中棧頂?shù)膁ouble類型數(shù)據(jù)存儲到局部變量表下標(biāo)為10的位置
3、窄化類型轉(zhuǎn)換(Narrowing Numeric Conversion)
3.1殴穴、轉(zhuǎn)換規(guī)則
- 從int類型 --> byte凉夯、short 或者 char 類型。對應(yīng)的指令有:i2b采幌、i2c劲够、i2s
- 從long類型 --> int類型。對應(yīng)的指令有:l2i
- 從float類型 --> long類型或者int類型休傍。對應(yīng)的指令有:f2l征绎、f2i
- 從double類型 --> int、long 或者 float類型磨取。對應(yīng)的指令有:d2i炒瘸、d2l、d2f
3.2寝衫、精度損失問題
- 窄化類型轉(zhuǎn)換可能會導(dǎo)致轉(zhuǎn)換結(jié)果具備不同的正負號、不同的數(shù)量級拐邪,因此慰毅,轉(zhuǎn)換過程很可能會導(dǎo)致數(shù)值丟失精度。
- 盡管數(shù)據(jù)類型窄化轉(zhuǎn)換可能會發(fā)生上限溢出扎阶、下限溢出和精度丟失等情況汹胃,但是Java虛擬機規(guī)范中明確規(guī)定數(shù)值類型的窄化轉(zhuǎn)換指令永遠不可能導(dǎo)致虛擬機拋出運行時異常
3.3、說明
-
3.3.1:當(dāng)一個浮點值窄化轉(zhuǎn)換為整數(shù)類型T(T限于int或long類型之一)的時候东臀,將遵循以下轉(zhuǎn)換規(guī)則
- 如果浮點值是NaN着饥,那轉(zhuǎn)換結(jié)果就是int或long類型的0
- 如果浮點值不是無窮大的話,浮點值使用IEEE 754 的向零舍入模式取整惰赋,獲取整數(shù)值v宰掉,如果v在目標(biāo)類型T(int或long)的表示范圍之內(nèi),那轉(zhuǎn)換結(jié)果就是v赁濒。否則轨奄,將根據(jù)v的符號,轉(zhuǎn)換為T所能表示的最大或者最小正數(shù)
-
3.3.2:當(dāng)將一個 double 類型窄化轉(zhuǎn)換為 float 類型時拒炎,將遵循以下轉(zhuǎn)換規(guī)則:通過向最接近數(shù)舍入模式舍入一個可以使用float類型表示的數(shù)字挪拟。最后結(jié)果根據(jù)下面這3條規(guī)則判斷:
- 如果轉(zhuǎn)換結(jié)果的絕對值太小而無法使用 float 來表示,將返回 float類型的正負0
- 如果轉(zhuǎn)換結(jié)果的絕對值太大而無法使用 float來表示击你,將返回 float類型的正負無窮大
- 對于 double 類型的 NaN值將按規(guī)定轉(zhuǎn)換為 float類型的 NaN值
3.4玉组、實例
- 代碼
/**
* 窄化類型轉(zhuǎn)換
*/
public void downCast2() {
int i = 10;
byte b = (byte) i;
short s = (short) i;
char c = (char) i;
long l = 10L;
int i1 = (int) l;
byte b1 = (byte) l;
}
- 字節(jié)碼
0 bipush 10
2 istore_1
3 iload_1
4 i2b
5 istore_2
6 iload_1
7 i2s
8 istore_3
9 iload_1
10 i2c
11 istore 4
13 ldc2_w #2 <10>
16 lstore 5
18 lload 5
20 l2i
21 istore 7
23 lload 5
25 l2i
26 i2b
27 istore 8
29 return
- 注意:在long類型轉(zhuǎn)byte類型時谎柄,先從long類型轉(zhuǎn)為int類型,再從int類型轉(zhuǎn)為byte類型
3.5惯雳、精度問題
- code
public void downCast3() {
double d = Double.NaN;
int i = (int) d;
System.out.println(d);
System.out.println(i);
}
- 輸出
NaN
0
3.5朝巫、無窮大窄化精度問題
- code
public void downCast4() {
double d = Double.POSITIVE_INFINITY;
long l = (long) d;
int i = (int) d;
System.out.println(d);
System.out.println(Double.MAX_VALUE);
System.out.println(l);
System.out.println(Long.MAX_VALUE);
System.out.println(i);
System.out.println(Integer.MAX_VALUE);
}
- 輸出
Infinity
1.7976931348623157E308
9223372036854775807
9223372036854775807
2147483647
2147483647
六、對象的創(chuàng)建與訪問指令
Java是面向?qū)ο蟮某绦蛟O(shè)計語言吨凑,虛擬機平臺從字節(jié)碼層面就對面向?qū)ο笞隽松顚哟蔚闹С趾赐帷S幸幌盗兄噶顚iT用于對象操作,可進一步細分為創(chuàng)建指令鸵钝、字段訪問指令糙臼、數(shù)組操作指令、類型檢查指令
1恩商、創(chuàng)建指令
雖然類實例和數(shù)組都是對象变逃,但Java虛擬機對類實例和數(shù)組的創(chuàng)建與操作使用了不同的字節(jié)碼指令
1.1、創(chuàng)建類實例的指令:new
- 接收一個操作數(shù)怠堪,為指向常量池的索引揽乱,表示要創(chuàng)建的類型,執(zhí)行完成后粟矿,將對象的引用壓入棧
1.2凰棉、創(chuàng)建數(shù)組的指令
- newarray:創(chuàng)建基本類型數(shù)組
- anewarray:創(chuàng)建引用類型數(shù)組
- multianewarray:創(chuàng)建多維數(shù)組
1.3、實例
- code
public void newArray() {
int[] intArray = new int[10];
Object[] objectArray = new Object[10];
int[][] intMintArray = new int[10][10];
String[][] stringMintArray = new String[10][];
}
- 字節(jié)碼
0 bipush 10
2 newarray 10 (int)
4 astore_1
5 bipush 10
7 anewarray #2 <java/lang/Object>
10 astore_2
11 bipush 10
13 bipush 10
15 multianewarray #3 <[[I> dim 2
19 astore_3
20 bipush 10
22 anewarray #4 <[Ljava/lang/String;>
25 astore 4
27 return
intArray 使用的是 newarray
objectArray 使用的是 anewarray
intMintArray 使用的是multianewarray
stringMintArray 使用的是anewarray陌粹,同樣是二維數(shù)組為什么使用的不是multianewarray指令撒犀?
code
public void newArray() {
int[] intArray = new int[10];
Object[] objectArray = new Object[10];
int[][] intMintArray = new int[10][10];
String[][] stringMintArray = new String[10][10];
}
- 字節(jié)碼
0 bipush 10
2 newarray 10 (int)
4 astore_1
5 bipush 10
7 anewarray #2 <java/lang/Object>
10 astore_2
11 bipush 10
13 bipush 10
15 multianewarray #3 <[[I> dim 2
19 astore_3
20 bipush 10
22 bipush 10
24 multianewarray #4 <[[Ljava/lang/String;> dim 2
28 astore 4
30 return
- 小結(jié)
- 只有明確了二維數(shù)組的數(shù)量后,才會使用multianewarray指令
2掏秩、字段訪問指令
對象創(chuàng)建后或舞,就可以通過對象訪問指令獲取對象實例或數(shù)組中的字段或者數(shù)組元素
- 訪問類字段(static字段,或者稱為類變量)的指令:getstatic蒙幻、putstatic
- 訪問類實例字段(非static字段映凳,或者稱為實例變量)的指令:getfield、putfield
實例
- code
public void setOrderId() {
Order order = new Order();
order.id = 1001;
System.out.println(order.id);
Order.name = "ORDER";
System.out.println(Order.name);
}
- 字節(jié)碼
0 new #8 <com/lkty/loadandstore/Order>
3 dup
4 invokespecial #9 <com/lkty/loadandstore/Order.<init>>
7 astore_1
8 aload_1
9 sipush 1001
12 putfield #10 <com/lkty/loadandstore/Order.id>
15 getstatic #5 <java/lang/System.out>
18 aload_1
19 getfield #10 <com/lkty/loadandstore/Order.id>
22 invokevirtual #11 <java/io/PrintStream.println>
25 ldc #12 <ORDER>
27 putstatic #13 <com/lkty/loadandstore/Order.name>
30 getstatic #5 <java/lang/System.out>
33 getstatic #13 <com/lkty/loadandstore/Order.name>
36 invokevirtual #7 <java/io/PrintStream.println>
39 return
3邮破、數(shù)組操作指令
3.1數(shù)組操作指令主要有:xastore和xaload指令
- 把一個數(shù)組元素加載到操作數(shù)棧的指令:baload诈豌、caload、saload决乎、saload队询、iaload、laload构诚、faload蚌斩、daload、aaload
- 將一個操作數(shù)棧的值存儲到數(shù)組元素中的指令:bastore、castore送膳、sastore员魏、lastore、fastore叠聋、dastore撕阎、aastore
-
取數(shù)組長度的指令:arraylength。該指令彈出棧頂?shù)臄?shù)組元素碌补,獲取數(shù)組的長度虏束,將長度壓入棧
3.2、說明
- 指令xaload:表示將數(shù)組的元素壓棧厦章,如saload镇匀、caload分別表示壓入short數(shù)組和char數(shù)組。指令xaload在執(zhí)行時袜啃,要求操作數(shù)中棧頂元素為數(shù)組索引i汗侵,棧頂順序位第2個元素為數(shù)組引用a,該指令會彈出棧頂這兩個元素群发,并將a[i]重新壓入堆棧
- 指令xastore:表示將棧中數(shù)據(jù)存儲到數(shù)組中晰韵,如iastore,它用于給一個int數(shù)組的給定索引賦值熟妓。在iastore執(zhí)行前雪猪,操作數(shù)棧頂需要以此準(zhǔn)備3個元素:值、索引起愈、數(shù)組引用浪蹂,iastore會彈出這3個值,并將值賦給數(shù)組中指定索引的位置
實例
- code
public void arrayTest() {
int[] intArray = new int[10];
intArray[1] = 10;
int indexValue = intArray[1];
}
- 字節(jié)碼
0 bipush 10
2 newarray 10 (int)
4 astore_1
5 aload_1
6 iconst_1
7 bipush 10
9 iastore
10 aload_1
11 iconst_1
12 iaload
13 istore_2
14 return
4告材、類型檢查指令
檢查類實例或數(shù)組類型的指令:instanceof、checkcast
- checkcast指令用于檢查類型前置轉(zhuǎn)換是否可以進行古劲。如果可以進行斥赋,那么checkcast指令不會改變操作數(shù)棧,否則它會拋出 ClassCastException 異常
- 指令 instanceof 用來判斷給定對象是否是某一個類的實例产艾,它會將判斷結(jié)果壓入操作數(shù)棧
七疤剑、方法調(diào)用與返回指令
1、方法調(diào)用指令
1.1闷堡、invokevirtual指令
- 用于調(diào)用對象的實例方法隘膘,根據(jù)對象的實際類型進行分派(虛方法分派),支持多態(tài)杠览。這也是Java語言中“最常見的方法分派方式”
- 分派調(diào)用哪些“有可能實現(xiàn)重寫的實例方法”
1.2弯菊、invokeInterface指令
- 用于調(diào)用“接口方法”,它會在運行時搜索由特定對象所實現(xiàn)的這個接口方法踱阿,并找出適合的方法進行調(diào)用
1.3管钳、invokespecial指令
- 用于調(diào)用一些需要特殊處理的實例方法钦铁,如:實例初始化方法(構(gòu)造器)、私有方法和父類方法才漆。這些方法都是“靜態(tài)類型綁定的”牛曹,不會在調(diào)用時進行動態(tài)派發(fā)
1.4、invokestatic指令
- 用于調(diào)用命名類中的類方法(static方法)醇滥,這是“靜態(tài)綁定”的
1.5黎比、invokedynamic指令
- 調(diào)用“動態(tài)綁定”的方法,這個是JDK7后新加入的指令鸳玩。用于在運行時動態(tài)解析出調(diào)用點限定符所引用的方法阅虫,并執(zhí)行該方法。前面4條調(diào)用指令的分派邏輯都固化在 Java 虛擬機內(nèi)部怀喉,而invokedynamic指令的分派邏輯是由用戶所設(shè)定的引導(dǎo)方法決定的
2书妻、方法返回指令
方法調(diào)用結(jié)束前,需要進行返回躬拢。方法返回指令是“根據(jù)返回值的類型區(qū)分”的
- ireturn(boolean躲履、byte、char聊闯、short工猜、int類型時使用)、lreturn菱蔬、freturn篷帅、dreturn和areturn
- 還有一條return指令供聲明為void的方法、實例初始化方法以及類和接口的類初始化使用
-
如果當(dāng)前返回的是synchronized方法拴泌,那么還會執(zhí)行一個隱含的monitorexit指令魏身,退出臨界區(qū)
八、操作數(shù)棧管理指令
如同操作一個普通數(shù)據(jù)結(jié)構(gòu)中的堆棧那樣蚪腐,JVM提供的操作數(shù)棧管理指令箭昵,可以用于直接操作操作數(shù)棧的指令
1、操作數(shù)棧管理指令
- 將一個或兩個元素從棧頂彈出回季,并且直接廢棄:pop家制、pop2
- 復(fù)制棧頂一個或兩個數(shù)值并將復(fù)制值或雙份的復(fù)制值重新壓入棧頂dup、dup2泡一,dup_x1颤殴,dup2_x1,dup_x2鼻忠,dup2_x2
- 將棧最頂端的兩個Slot數(shù)值位置交換:swap涵但。Java虛擬機沒有提供交換兩個64位數(shù)據(jù)類型(long、double)數(shù)值的指令
- 指令nop灰羽,是一個非常特殊的指令呵燕,它的字節(jié)碼為0x00.和匯編語言中nop一樣腰素,它表示什么都不做固额。這條指令一般可用于調(diào)試屹堰、占位等
2蛤铜、說明
2.1瞬内、不帶_x的指令是復(fù)制棧頂數(shù)據(jù)并壓入棧頂藻治。包括兩個指令埋涧,dup和dup2板辽,dup的系數(shù)代表要復(fù)制的Slot個數(shù)
- dup開頭的指令用于復(fù)制1個Slot的數(shù)據(jù)(1個int或1個reference類型數(shù)據(jù))。
- dup2開頭的指令用于復(fù)制2個Slot的數(shù)據(jù)(1個long或2個int或1個int+1個float類型數(shù)據(jù))
2.2棘催、帶_x的指令是復(fù)制棧頂數(shù)據(jù)并插入棧頂一下的某個位置劲弦。共有4個指令,dup_x1醇坝,dup2_x1邑跪,dup_x2,dup2_x2呼猪。對于帶_x的復(fù)制插入指令画畅,只要將指令的dup和x的系數(shù)相加,結(jié)果即為需要插入的位置宋距。
- dup_x1插入位置:1+1=2轴踱,即棧頂2個Slot下面
- dup_x2插入位置:1+2=3,即棧頂3個Slot下面
- dup2_x1插入位置:2+1=3谚赎,即棧頂3個Slot下面
- dup2_x2插入位置:2+2=4淫僻,即棧頂4個Slot下面
- pop:將棧頂?shù)?個Slot數(shù)值出棧。如1個short類型數(shù)值
- pop2:將棧頂?shù)?個Slot數(shù)值出棧壶唤。如1個double類型數(shù)值雳灵,或者2個int類型數(shù)值
九、控制轉(zhuǎn)移指令
程序流程離不開條件控制闸盔,為了支持條件跳轉(zhuǎn)细办,虛擬機提供了大量字節(jié)碼指令,大體上可以分為:
1蕾殴、比較指令
1.1、比較指令說明
- 比較指令的作用是比較棧頂兩個元素的大小岛啸,并將比較結(jié)果入棧
- 比較指令有:dcmpg钓觉、dcmpl、fcmpg坚踩、fcmpl荡灾、lcmp
- 首字符d表示double類型,f表示float類型,l表示long類型
2批幌、條件跳轉(zhuǎn)指令
-
條件跳轉(zhuǎn)指令有:ifeq础锐、iflt、ifle荧缘、ifne皆警、ifgt、ifge截粗、ifnull信姓、ifnonnull。這些指令都接收兩個字節(jié)的操作數(shù)绸罗,用于計算跳轉(zhuǎn)的位置
- 對于boolean意推、byte、char珊蟀、short類型的條件分支比較操作菊值,都是使用int類型的比較指令完成
- 對于long、float育灸、double類型的條件分支比較操作腻窒,則會先執(zhí)行相應(yīng)類型的比較運算指令,運算指令返回一個整型值到操作數(shù)棧中描扯,隨后再執(zhí)行int類型的條件分支比較操作來完成整個分支跳轉(zhuǎn)
- 由于各類型的比較最終都會轉(zhuǎn)為int類型的比較操作定页,所以Java虛擬機提供的int類型的條件分支指令是最為豐富和強大的
3、比較條件跳轉(zhuǎn)指令
- 比較條件跳轉(zhuǎn)指令類似于比較指令和條件跳轉(zhuǎn)指令的結(jié)合體绽诚,它將比較和跳轉(zhuǎn)兩個步驟合二為一
-
比較條件跳轉(zhuǎn)指令有:if_icmpeq典徊、if_icmpne、if_icmplt恩够、if_icmpgt卒落、if_icmple、if_icmpge蜂桶、if_acmpeq和if_acmpne儡毕。
4、多條件分支跳轉(zhuǎn)指令
-
多條件分支跳轉(zhuǎn)指令是專為switch-case語句設(shè)計的扑媚,這樣有tableswitch和lookupswitch
- tableswitch:要求“多個條件分支值是連續(xù)的”腰湾,它內(nèi)部只存放起始值和終止值,以及若干個跳轉(zhuǎn)偏移量疆股,通過給定的操作數(shù)index费坊,可以立即定位到跳轉(zhuǎn)偏移量位置,因此效率比較高
- 指令lookupswitch內(nèi)部存放這各個離散的case-offset對旬痹,每次執(zhí)行都要搜索全部的case-offset對附井,找到匹配的case值讨越,并根據(jù)對應(yīng)的offset計算跳轉(zhuǎn)地址,因此效率較低永毅。處于提升效率考慮把跨,先使用hashcode值排序,然后在使用equal判斷
5沼死、無條件跳轉(zhuǎn)指令
- 目前主要的無條件跳轉(zhuǎn)指令為goto着逐。goto指令接收兩個字節(jié)的操作數(shù),共同組成一個帶符號的整數(shù)漫雕,用于指定指令的偏移量滨嘱,指令執(zhí)行的目的就是跳轉(zhuǎn)到偏移量給定的位置處
-
指令偏移量太大,超過雙字節(jié)的帶符號整數(shù)的范圍浸间,則可以使用指令goto_w太雨,它和goto有相同的作用,但是它接收4個字節(jié)的操作數(shù)魁蒜,可以表示更大的地址范圍
十囊扳、異常處理指令
1、拋出異常指令
1.1兜看、athrow指令
- Java程序中顯示拋出異常的操作(throw語句)都是有athrow指令來實現(xiàn)锥咸。除了使用throw語句顯示拋出異常情況之外,“JVM規(guī)范還規(guī)定了許多運行時異常會在其他Java虛擬機指令檢測到異常狀況時自動拋出”细移。
- 整車情況下搏予,操作數(shù)棧的壓入彈出都是一條條指令完成的。唯一的例外情況是在拋異常時弧轧,Java虛擬機會清除操作數(shù)棧上的所有內(nèi)容雪侥,而后將異常實例壓入調(diào)用者曹鎖數(shù)棧上
2、異常處理與異常表
2.1精绎、處理異常
- 在Java虛擬機中速缨,處理異常(catch語句)不是由字節(jié)碼指令來實現(xiàn)的(早期使用jsr、ret指令)代乃,而是“采用異常表來完成的”
2.2旬牲、異常表
- 如果一個方法定義了一個try-catch或try-finally的異常處理,就會創(chuàng)建一個異常表搁吓。它包含了每個異常處理或者finally塊的信息
- 起始位置
- 結(jié)束位置
- 程序計數(shù)器記錄的代碼處理的偏移地址
- 被捕獲的異常類在常量池中的索引
- 當(dāng)一個異常被拋出時原茅,JVM會在當(dāng)前的方法里尋找一個匹配的處理,如果沒有找到堕仔,這個發(fā)給你法會強制結(jié)束并彈出當(dāng)前棧幀擂橘,并且異常會重新給上層調(diào)用的方法(在調(diào)用方法棧幀)。如果在所有棧幀彈出前任然沒有找到哦啊合適的異常處理贮预,這個線程將終止贝室。如果這個異常在最后一個非守護線程里拋出,將會導(dǎo)致JVM自己終止仿吞。比如這個線程是個main線程
-
不管什么時候拋出異常滑频,如果異常處理最終匹配了所有一次類型,代碼就會繼續(xù)執(zhí)行唤冈。在這種情況下峡迷,如果發(fā)給你法結(jié)束后沒有拋出異常,仍然執(zhí)行finally塊你虹,在return前绘搞,它直接跳到finally塊來完成目標(biāo)。
十一傅物、同步控制指令
Java虛擬機支持兩種同步結(jié)構(gòu):“方法級的同步”和“方法內(nèi)部一段指令序列的同步”夯辖,這兩種同步都是使用 monitor 來支持的
1、方法級的同步
- 方法級的同步是隱式的董饰,即無需通過字節(jié)碼指令來控制蒿褂,它實現(xiàn)在方法調(diào)用和返回操作之中。虛擬機可以從方法常量池的方法表結(jié)構(gòu)中的ACC_SYNCHRONIZED 訪問標(biāo)志得知一個方法是否聲明為同步方法
- 當(dāng)調(diào)用方法時卒暂,調(diào)用指令將會檢查方法的ACC_SYNCHRONIZED訪問標(biāo)志是否設(shè)置
- 如果設(shè)置了啄栓,執(zhí)行線程將先持有同步鎖,然后執(zhí)行方法也祠。最后在方法完成(無論是正常完成還是非正常完成)時釋放同步鎖
- 在方法執(zhí)行期間昙楚,執(zhí)行線程持有了同步鎖,其他任何線程都無法再獲得同一個鎖
- 如果一個同步方法執(zhí)行期間拋出了異常诈嘿,并且在方法內(nèi)部無法處理此異常堪旧,那這個同步方法所持有的鎖將在異常拋到同步方法之外時自動釋放
2、方法內(nèi)指定指令序列的同步
- 同步一段指令集序列:通常是由Java中的synchronized語句塊來表示的永淌。jvm的指令集有monitorenter和monitorexit兩條指令來支持 synchronized 關(guān)鍵字的語義
- 當(dāng)一個線程進入同步代碼塊時崎场,它使用monitorenter指令請求進入。如果當(dāng)前對象的監(jiān)視器計數(shù)器為0遂蛀,則它會被準(zhǔn)許進入谭跨,若為1,則判斷持有當(dāng)前監(jiān)視器的線程是否為自己李滴,如果是螃宙,則進入,否則進行等待所坯,知道對象的監(jiān)視計數(shù)器為0谆扎,才會被允許進入同步塊
- 當(dāng)線程退出同步塊時,需要使用monitorexit聲明退出芹助。在Java虛擬機中堂湖,任何對象都有一個監(jiān)視器與之相關(guān)聯(lián)闲先,用來判斷對象是否被鎖定
- 指令monitorenter和monitorexit在執(zhí)行時,都需要在操作數(shù)棧頂壓入對象无蜂,之后monitorenter和monitorexit的鎖定和釋放都是針對這個對象的監(jiān)視器進行的
-
只有當(dāng)線程4離開臨界區(qū)后伺糠,線程1、2斥季、3才有可能進入
- 小結(jié):
- 編譯器必須確保無論方法通過何種方式完成训桶,方法中調(diào)用過的每條monitorenter指令都必須執(zhí)行其對應(yīng)的monitorexit指令,而無論這個方法是正常結(jié)束還是異常結(jié)束
- 為了保證在方法移除完成時monitorenter和monitorexit指令依然可以正確配對執(zhí)行酣倾,“編譯器會自動產(chǎn)生一個異常處理器舵揭,這個異常處理器聲明可處理所有的異常”躁锡,它的目的就是用來執(zhí)行monitorexit指令