深入淺出JVM(十)之位元組碼指令(下篇)

發表於2024-03-03

上篇文章深入淺出JVM(九)之位元組碼指令(上篇)已經深入淺出說明載入儲存、算術、型別轉換的位元組碼指令,本篇文章作為位元組碼的指令的下篇,深入淺出的解析各種型別位元組碼指令,如:方法呼叫與返回、控制轉義、異常處理、同步等

使用idea中的外掛jclasslib檢視編譯後的位元組碼指令

方法呼叫與返回指令

方法呼叫指令

非虛方法: 靜態方法,私有方法,父類中的方法,被final修飾的方法,例項構造器

與之對應不是非虛方法的就是虛方法了

  • 普通呼叫指令

    • invokestatic: 呼叫靜態方法
    • invokespecial: 呼叫私有方法,父類中的方法,例項構造器<init>方法,final方法
    • invokeinterface: 呼叫介面方法
    • invokevirtual: 呼叫虛方法

    使用invokestaticinvokespecial指令的一定是非虛方法

    使用invokeinterface指令一定是虛方法(因為介面方法需要具體的實現類去實現)

    使用invokevirtual指令可能是虛方法

  • 動態呼叫指令

    • invokedynamic: 動態解析出需要呼叫的方法再執行

    jdk 7 出現invokedynamic,支援動態語言

測試虛方法程式碼
  • 父類
 public class Father {
     public static void staticMethod(){
         System.out.println("father static method");
     }
 ​
     public final void finalMethod(){
         System.out.println("father final method");
     }
 ​
     public Father() {
         System.out.println("father init method");
     }
 ​
     public void overrideMethod(){
         System.out.println("father override method");
     }
 }
  • 介面
 public interface TestInterfaceMethod {
     void testInterfaceMethod();
 }
  • 子類
 public class Son extends Father{
 ​
     public Son() {
         //invokespecial 呼叫父類init 非虛方法
         super();
         //invokestatic 呼叫父類靜態方法 非虛方法
         staticMethod();
         //invokespecial 呼叫子類私有方法 特殊的非虛方法
         privateMethod();
         //invokevirtual 呼叫子類的重寫方法 虛方法
         overrideMethod();
         //invokespecial 呼叫父類方法 非虛方法
         super.overrideMethod();
         //invokespecial 呼叫父類final方法 非虛方法
         super.finalMethod();
         //invokedynamic 動態生成介面的實現類 動態呼叫
         TestInterfaceMethod test = ()->{
             System.out.println("testInterfaceMethod");
         };
         //invokeinterface 呼叫介面方法 虛方法
         test.testInterfaceMethod();
     }
 ​
     @Override
     public void overrideMethod(){
         System.out.println("son override method");
     }
 ​
     private void privateMethod(){
         System.out.println("son private method");
     }
 ​
     public static void main(String[] args) {
         new Son();
     }
 }

image-20210426234249850.png

方法返回指令

方法返回指令: 方法結束前,將棧頂元素(最後一個元素)出棧 ,返回給呼叫者

根據方法的返回型別劃分多種指令

image-20210515103425506.png

運算元棧管理指令

通用型指令,不區分型別

  • 出棧

    • pop/pop2出棧1個/2個棧頂元素
  • 入棧

    • dup/dup2 複製棧頂1個/2個slot並重新入棧
    • dup_x1 複製棧頂1個slot並插入到棧頂開始的第2個slot下
    • dup_x2複製棧頂1個slot並插入到棧頂開始的第3個slot下
    • dup2_x1複製棧頂2個slot並插入到棧頂開始的第3個slot下
    • dup2_x2複製棧頂2個slot並插入到棧頂開始的第4個slot下

      • 插入到具體的slot計算: dup的係數 + _x的係數

控制轉義指令

條件跳轉指令

通常先進行比較指令,再進行條件跳轉指令

比較指令比較結果-1,0,1再進行判斷是否要跳轉

條件跳轉指令: 出棧棧頂元素,判斷它是否滿足條件,若滿足條件則跳轉到指定位置

image-20210515164609270.png

image-20210515165351883.png

注意: 這種跳轉指令一般都"取反",比如程式碼中第一個條件語句是d>100,它第一個條件跳轉指令就是ifle小於等於0,滿足則跳轉,不滿足則按照順序往下走

比較條件跳轉指令

比較條件跳轉指令 類似 比較指令和條件跳轉指令 的結合體

image-20210515180004587.png

image-20210515181000595.png

多條件分支跳轉指令

多條件分支跳轉指令是為了switch-case提出的

tableswitch用於case值連續的switch多條件分支跳轉指令,效率好

lookupswitch用於case值不連續的switch多條件分支跳轉指令(雖然case值不連續,但最後會對case值進行排序)

tableswitch

image-20210515182307183.png

lookupswitch

image-20210515183527055.png

對於String型別是先找到對應的雜湊值再equals比較確定走哪個case的

無條件跳轉指令

無條件跳轉指令就是跳轉到某個位元組碼指令處

goto經常使用

jsr,jsr_w,ret不怎麼使用了

image-20210515183640270.png

異常處理指令

throw丟擲異常對應athrow: 清除該運算元棧上所有內容,將異常例項壓入呼叫者運算元棧上

使用try-catch/try-final/throws時會產生異常表

異常表儲存了異常處理資訊 (起始、結束位置、位元組碼指令偏移地址、異常類在常量池中的索引等資訊)

athrow

image-20210515192750444.png

異常表

image-20210515193437666.png

異常還會被壓入棧或者儲存到異常表中

同步控制指令

synchronized作用於方法時,方法的訪問標識會有ACC_SYNCHRONIZED表示該方法需要加鎖

synchronized作用於某個物件時,對應著monitorentry加鎖位元組碼指令和 monitorexit解鎖位元組碼指令

Java中的synchronized預設是可重入鎖

  • 當執行緒要訪問需要加鎖的物件時 (執行monitorentry)
  1. 先檢視物件頭中加鎖次數,如果為0說明未加鎖,獲取後,加鎖次數自增
  2. 如果不為0,再檢視獲取鎖的執行緒是不是自己,如果是自己就可以訪問,加鎖次數自增
  3. 如果不為0且獲取鎖執行緒不是自己,就阻塞

當執行緒釋放鎖時 (執行monitorexit)會讓加鎖次數自減

image-20210515195912727.png

為什麼會有2個monitorexit ?

程式正常執行應該是一個monitorentry對應一個monitorexit的

如果程式在加鎖的程式碼中丟擲了異常,沒有釋放鎖,那不就會造成其他阻塞的執行緒永遠也拿不到鎖了嗎

所以在程式丟擲異常時(跳轉PC偏移量為15的指令)繼續往下執行,丟擲異常前要釋放鎖

總結

本篇文章作為位元組碼指令的下篇,深入淺出的解析方法呼叫與返回,運算元棧的入棧、出棧,控制轉義,異常和同步相關位元組碼指令

方法呼叫指令分為靜態、私有、介面、虛、動態方法等,返回指令則主要是以i、l、f、d、a開頭的return指令分別處理不同型別的返回值

運算元棧中的出棧指令常用pop相關指令,入棧(複製棧頂元素並插入)常用dup相關指令

控制轉義指令中條件跳轉指令是判斷棧頂元素來進行跳轉,比較條件跳轉指令是透過兩個棧頂元素比較來判斷跳轉,多條件分支跳轉是滿足switch,常在異常時進行goto無條件跳轉

異常處理指令用於丟擲異常,清除運算元棧並將異常壓入呼叫者運算元棧頂

同步控制指令常使用monitorentrymonitoryexit,為了防止異常時死鎖,拋異常前執行monitoryexit

最後(一鍵三連求求拉~)

本篇文章筆記以及案例被收入 gitee-StudyJavagithub-StudyJava 感興趣的同學可以stat下持續關注喔\~

有什麼問題可以在評論區交流,如果覺得菜菜寫的不錯,可以點贊、關注、收藏支援一下\~

關注菜菜,分享更多幹貨,公眾號:菜菜的後端私房菜

本文由部落格一文多發平臺 OpenWrite 釋出!

相關文章