28-字節(jié)碼指令

一瘦馍、概述

  • 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é)果”


    image.png
5睦刃、局部變量表(Local Variables)
  • Java方法棧幀的另外一個重要組成部分則是局部變量區(qū)砚嘴,字節(jié)碼程序可以將計算的結(jié)果緩存在局部變量區(qū)之中。實際上,Java虛擬機將局部變量區(qū)當(dāng)成一個數(shù)組际长,依次存放 this 指針(僅非靜態(tài)方法)耸采,所傳入的參數(shù),以及字節(jié)碼中的局部變量
  • long類型以及double類型的值將占據(jù)兩個單元工育,其余類型僅占據(jù)一個單元


    image.png
  • 在棧幀中虾宇,與性能調(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
    image.png

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é)
image.png
  • 代碼
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ù)棧”
    image.png
  • 2 fstore_1:操作數(shù)出棧存入到局部變量表下標(biāo)為1的位置
    image.png
  • 3 fload_1:加載局部變量表中數(shù)據(jù)到操作數(shù)棧
    image.png
  • 4 fneg:取出操作數(shù)棧棧頂元素取反
    image.png
  • 5 fstore_2:操作數(shù)棧棧頂數(shù)據(jù)存儲到局部變量表下標(biāo)為2的位置上
    image.png
  • 6 fload_2:加載局部變量表位置2的數(shù)據(jù)到操作數(shù)棧中
    image.png
  • 7 fneg:取出操作數(shù)棧棧頂元素取反
    image.png
  • 8 fstore_1:取出操作數(shù)棧棧頂元素存儲到局部變量表下標(biāo)為1的位置
    image.png
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ù)棧
    image.png
  • 2 istore_1:把操作數(shù)棧棧頂元素數(shù)據(jù)存入局部變量表下標(biāo)為1的位置
    image.png
  • 3 iload_1:加載局部變量表下標(biāo)1中的數(shù)據(jù)放入到操作數(shù)棧
    image.png
  • 4 bipush 10:把常量10放入操作數(shù)棧
    image.png
  • 6 iadd:取出操作數(shù)棧前2位數(shù)據(jù)相加后再放入操作數(shù)棧
    image.png
  • 7 istore_1:把操作數(shù)棧頂元素存儲到局部變量表下標(biāo)為1的位置
    image.png
: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ù)棧
    image.png
  • 2 istore_1:把操作數(shù)棧棧頂元素數(shù)據(jù)存入局部變量表下標(biāo)為1的位置
    image.png
  • 3 iinc 1 by 10:局部變量下標(biāo)為1的數(shù)據(jù)加10
    image.png
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ù)棧
    image.png
  • 2 istore_1:操作數(shù)棧棧頂數(shù)據(jù)存放到局部變量表下標(biāo)為1的位置
    image.png
  • 3 iinc 1 by 1:局部變量表下標(biāo)1的數(shù)據(jù)+1
    image.png
3.2、i++字節(jié)碼分析結(jié)果與++i一樣
image.png

實例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ù)棧
    image.png
  • 2 istore_1:把操作數(shù)棧頂數(shù)據(jù)存儲到局部變量表下標(biāo)為1的位置上
    image.png
  • 3 iload_1:加載局部變量表下標(biāo)為1的數(shù)據(jù)到操作數(shù)棧中
    image.png
  • 4 iinc 1 by 1:局部變量表下標(biāo)為1的數(shù)據(jù)加1
    image.png
  • 7 istore_2:將操作數(shù)棧頂數(shù)據(jù)存入到局部變量表下標(biāo)為2的位置
    image.png
實例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ù)棧
    image.png
  • 2 istore_1:將操作數(shù)棧頂數(shù)據(jù)存儲到局部變量表下標(biāo)為1的位置中
    image.png
  • 3 iinc 1 by 1:局部變量表下標(biāo)為1的數(shù)據(jù)+1
    image.png
  • 6 iload_1:加載局部變量表下標(biāo)1中數(shù)據(jù)到操作數(shù)棧中
    image.png
  • 7 istore_2:將操作數(shù)棧中的數(shù)據(jù)存儲到局部變量表下標(biāo)為2的位置中
    image.png

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ù)棧中
    image.png
  • 2 istore_1:將操作數(shù)棧中棧頂數(shù)據(jù)存儲到局部變量表下標(biāo)為1的位置
    image.png
  • 3 iload_1:將局部變量表下標(biāo)為1中的數(shù)據(jù)加載到操作數(shù)棧中
    image.png
  • 4 i2l:將int類型數(shù)據(jù)轉(zhuǎn)換為long類型數(shù)據(jù)

  • 5 lstore_2:將操作數(shù)棧中數(shù)據(jù)存儲到局部變量表下標(biāo)為2的位置
    image.png
  • 6 iload_1:將局部變量表下標(biāo)為1中的數(shù)據(jù)加載到操作數(shù)棧中
    image.png
  • 7 i2f:將int類型數(shù)據(jù)轉(zhuǎn)為float類型數(shù)據(jù)

  • 8 fstore 4:將操作數(shù)棧中棧頂?shù)膄loat類型數(shù)據(jù)存儲到局部變量表下標(biāo)為4的位置
    image.png
  • 10 iload_1:將局部變量表下標(biāo)為1中的數(shù)據(jù)加載到操作數(shù)棧中
    image.png
  • 11 i2d:將int類型數(shù)據(jù)轉(zhuǎn)換為double類型數(shù)據(jù)

  • 12 dstore 5:將操作數(shù)棧中棧頂?shù)膁ouble類型數(shù)據(jù)存儲到局部變量表下標(biāo)為5的位置上
    image.png
  • 14 lload_2:將局部變量表下標(biāo)為2中的long類型數(shù)據(jù)加載到操作數(shù)棧中
    image.png
  • 15 l2f:將long類型數(shù)據(jù)轉(zhuǎn)換為float類型數(shù)據(jù)

  • 16 fstore 7:將操作數(shù)棧中棧頂?shù)膄loat類型數(shù)據(jù)存儲到局部變量表下標(biāo)為7的位置
    image.png
  • 18 lload_2:將局部變量表下標(biāo)為2中的long類型數(shù)據(jù)加載到操作數(shù)棧中
    image.png
  • 19 l2d:將long類型的數(shù)據(jù)轉(zhuǎn)換為double類型數(shù)據(jù)

  • 20 dstore 8:將操作數(shù)棧中棧頂?shù)膁ouble類型數(shù)據(jù)存儲到局部變量表下標(biāo)為8的位置
    image.png
  • 22 fload 7:將局部變量表下標(biāo)為7中的float類型數(shù)據(jù)加載到操作數(shù)棧中
    image.png
  • 24 f2d:將float類型數(shù)據(jù)轉(zhuǎn)換為double類型數(shù)據(jù)

  • 25 dstore 10:將操作數(shù)棧中棧頂?shù)膁ouble類型數(shù)據(jù)存儲到局部變量表下標(biāo)為10的位置
    image.png

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ù)組的長度虏束,將長度壓入棧


    image.png
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ū)


    image.png

八、操作數(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)的位置


    image.png
  • 對于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儡毕。
    image.png

4、多條件分支跳轉(zhuǎn)指令

  • 多條件分支跳轉(zhuǎn)指令是專為switch-case語句設(shè)計的扑媚,這樣有tableswitch和lookupswitch
    image.png
  • 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ù)魁蒜,可以表示更大的地址范圍
    image.png

十囊扳、異常處理指令

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)。
    image.png

    image.png

十一傅物、同步控制指令

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才有可能進入
    image.png
image.png
  • 小結(jié):
    • 編譯器必須確保無論方法通過何種方式完成训桶,方法中調(diào)用過的每條monitorenter指令都必須執(zhí)行其對應(yīng)的monitorexit指令,而無論這個方法是正常結(jié)束還是異常結(jié)束
    • 為了保證在方法移除完成時monitorenter和monitorexit指令依然可以正確配對執(zhí)行酣倾,“編譯器會自動產(chǎn)生一個異常處理器舵揭,這個異常處理器聲明可處理所有的異常”躁锡,它的目的就是用來執(zhí)行monitorexit指令
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末午绳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子稚铣,更是在濱河造成了極大的恐慌箱叁,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惕医,死亡現(xiàn)場離奇詭異耕漱,居然都是意外死亡,警方通過查閱死者的電腦和手機抬伺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門螟够,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人峡钓,你說我怎么就攤上這事妓笙。” “怎么了能岩?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵寞宫,是天一觀的道長。 經(jīng)常有香客問我拉鹃,道長辈赋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任膏燕,我火速辦了婚禮钥屈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘坝辫。我一直安慰自己篷就,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布近忙。 她就那樣靜靜地躺著竭业,像睡著了一般智润。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上未辆,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天做鹰,我揣著相機與錄音,去河邊找鬼鼎姐。 笑死,一個胖子當(dāng)著我的面吹牛更振,可吹牛的內(nèi)容都是我干的炕桨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼肯腕,長吁一口氣:“原來是場噩夢啊……” “哼献宫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起实撒,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤姊途,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后知态,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捷兰,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年负敏,在試婚紗的時候發(fā)現(xiàn)自己被綠了贡茅。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡其做,死狀恐怖顶考,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情妖泄,我是刑警寧澤驹沿,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站蹈胡,受9級特大地震影響渊季,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜审残,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一梭域、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧搅轿,春花似錦病涨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赎懦。三九已至,卻和暖如春幻工,著一層夾襖步出監(jiān)牢的瞬間励两,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工囊颅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留当悔,地道東北人。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓踢代,卻偏偏與公主長得像盲憎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子胳挎,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,843評論 2 354

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