上篇文章深入淺出JVM(九)之位元組碼指令(上篇)已經深入淺出說明載入儲存、算術、型別轉換的位元組碼指令,本篇文章作為位元組碼的指令的下篇,深入淺出的解析各種型別位元組碼指令,如:方法呼叫與返回、控制轉義、異常處理、同步等
使用idea中的外掛jclasslib檢視編譯後的位元組碼指令
方法呼叫與返回指令
方法呼叫指令
非虛方法: 靜態方法,私有方法,父類中的方法,被final修飾的方法,例項構造器
與之對應不是非虛方法的就是虛方法了
普通呼叫指令
invokestatic
: 呼叫靜態方法invokespecial
: 呼叫私有方法,父類中的方法,例項構造器<init>方法,final方法invokeinterface
: 呼叫介面方法invokevirtual
: 呼叫虛方法
使用
invokestatic
和invokespecial
指令的一定是非虛方法使用
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();
}
}
方法返回指令
方法返回指令: 方法結束前,將棧頂元素(最後一個元素)出棧 ,返回給呼叫者
根據方法的返回型別劃分多種指令
運算元棧管理指令
通用型指令,不區分型別
出棧
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
的係數
- 插入到具體的slot計算: dup的係數 +
控制轉義指令
條件跳轉指令
通常先進行比較指令,再進行條件跳轉指令
比較指令比較結果-1,0,1再進行判斷是否要跳轉
條件跳轉指令: 出棧棧頂元素,判斷它是否滿足條件,若滿足條件則跳轉到指定位置
注意: 這種跳轉指令一般都"取反",比如程式碼中第一個條件語句是d>100,它第一個條件跳轉指令就是ifle
小於等於0,滿足則跳轉,不滿足則按照順序往下走
比較條件跳轉指令
比較條件跳轉指令 類似 比較指令和條件跳轉指令 的結合體
多條件分支跳轉指令
多條件分支跳轉指令是為了switch-case提出的
tableswitch
用於case值連續的switch多條件分支跳轉指令,效率好
lookupswitch
用於case值不連續的switch多條件分支跳轉指令(雖然case值不連續,但最後會對case值進行排序)
tableswitch
lookupswitch
對於String型別是先找到對應的雜湊值再equals比較確定走哪個case的
無條件跳轉指令
無條件跳轉指令就是跳轉到某個位元組碼指令處
goto
經常使用
jsr,jsr_w,ret
不怎麼使用了
異常處理指令
throw丟擲異常對應athrow
: 清除該運算元棧上所有內容,將異常例項壓入呼叫者運算元棧上
使用try-catch/try-final/throws時會產生異常表
異常表儲存了異常處理資訊 (起始、結束位置、位元組碼指令偏移地址、異常類在常量池中的索引等資訊)
athrow
異常表
異常還會被壓入棧或者儲存到異常表中
同步控制指令
synchronized作用於方法時,方法的訪問標識會有ACC_SYNCHRONIZED表示該方法需要加鎖
synchronized作用於某個物件時,對應著monitorentry
加鎖位元組碼指令和 monitorexit
解鎖位元組碼指令
Java中的synchronized預設是可重入鎖
- 當執行緒要訪問需要加鎖的物件時 (執行monitorentry)
- 先檢視物件頭中加鎖次數,如果為0說明未加鎖,獲取後,加鎖次數自增
- 如果不為0,再檢視獲取鎖的執行緒是不是自己,如果是自己就可以訪問,加鎖次數自增
- 如果不為0且獲取鎖執行緒不是自己,就阻塞
當執行緒釋放鎖時 (執行monitorexit)會讓加鎖次數自減
為什麼會有2個monitorexit ?
程式正常執行應該是一個monitorentry對應一個monitorexit的
如果程式在加鎖的程式碼中丟擲了異常,沒有釋放鎖,那不就會造成其他阻塞的執行緒永遠也拿不到鎖了嗎
所以在程式丟擲異常時(跳轉PC偏移量為15的指令)繼續往下執行,丟擲異常前要釋放鎖
總結
本篇文章作為位元組碼指令的下篇,深入淺出的解析方法呼叫與返回,運算元棧的入棧、出棧,控制轉義,異常和同步相關位元組碼指令
方法呼叫指令分為靜態、私有、介面、虛、動態方法等,返回指令則主要是以i、l、f、d、a開頭的return指令分別處理不同型別的返回值
運算元棧中的出棧指令常用pop
相關指令,入棧(複製棧頂元素並插入)常用dup
相關指令
控制轉義指令中條件跳轉指令是判斷棧頂元素來進行跳轉,比較條件跳轉指令是透過兩個棧頂元素比較來判斷跳轉,多條件分支跳轉是滿足switch,常在異常時進行goto
無條件跳轉
異常處理指令用於丟擲異常,清除運算元棧並將異常壓入呼叫者運算元棧頂
同步控制指令常使用monitorentry
和monitoryexit
,為了防止異常時死鎖,拋異常前執行monitoryexit
最後(一鍵三連求求拉~)
本篇文章筆記以及案例被收入 gitee-StudyJava、 github-StudyJava 感興趣的同學可以stat下持續關注喔\~
有什麼問題可以在評論區交流,如果覺得菜菜寫的不錯,可以點贊、關注、收藏支援一下\~
關注菜菜,分享更多幹貨,公眾號:菜菜的後端私房菜
本文由部落格一文多發平臺 OpenWrite 釋出!