JVM 位元組碼執行例項分析
前言
最近在看《Java 虛擬機器規範》和《深入理解JVM虛擬機器》,對於位元組碼的執行有了進一步的瞭解。位元組碼就像是組合語言,是 JVM 的指令集。下面我們先對 JVM 執行引擎做一下簡單介紹,然後根據例項分析 JVM 位元組碼的執行過程。包括:
- for 迴圈位元組碼分析
- try-catch-finally 位元組碼分析
執行時棧幀結構
棧幀是用於支援虛擬機器進行方法呼叫和方法執行的資料結構,它是虛擬機器執行時資料區中的虛擬機器棧的棧元素。棧幀儲存了方法的區域性變數表,運算元棧,動態連線和方法返回地址等資訊。每一個方法從呼叫開始至執行完成的過程,都對應著一個棧幀在虛擬機器棧裡面從入棧到出棧的過程。
在編譯程式設計師程式碼的時候,棧幀中區域性變數表和運算元棧的大小已經確定了,並且寫入到方法表中的 Code
屬性中。
在活動執行緒中,只有位於棧頂的棧幀才是有效的, 稱為當前棧幀,與這個棧幀關聯的方法稱為當前方法。執行引擎執行的所有位元組碼指令只對當前棧幀進行操作。
區域性變數表
區域性變數表是一組變數值儲存空間,用於存放方法引數和方法內部定義的區域性變數。區域性變數表的容量以變數槽(slot)為最小單位,每個 slot 保證能放下 32 位內的資料型別。虛擬機器通過索引定位的方式使用區域性變數表,索引值從 0 開始。值得注意的是,對於例項方法,區域性變數表中第 0 位索引的 slot 預設是 this
引用;靜態方法則不是。而且為了節約記憶體,slot 是可以重用的。
運算元棧
運算元棧的元素可以是任意的 Java 資料型別。當一個方法開始時,這個方法的運算元棧是空的,在方法的執行過程中,會有各種位元組碼指令往運算元棧中寫入和提取內容,也就是出棧入棧操作。
例項分析
下面分析的位元組碼指令主要是對區域性變數表和操作棧的讀寫。
for 迴圈位元組碼分析
void spin() { int i; for (i = 0; i < 100; i++) { ; // Loop body is empty } }
上面是一個空迴圈的程式碼,編譯後的位元組碼如下:
Method void spin() 0 iconst_0 // Push int constant 0 1 istore_1 // Store into local variable 1 (i=0) 2 goto 8 // First time through don’t increment 5 iinc 1 1 // Increment local variable 1 by 1 (i++) 8 iload_1 // Push local variable 1 (i) 9 bipush 100 // Push int constant 100 11 if_icmplt 5 // Compare and loop if less than (i < 100) 14 return // Return void when done
相信大家看到上面的程式碼都是一臉懵逼,即使有註釋還是不知道位元組碼到底做了什麼操作。下面我就圖解每一條指令,幫助理解。上面的程式碼都是對區域性變數表和運算元棧的操作,所以我們的關注點就在這兩個區域上。(棧是自頂向下的)
0 iconst_0 //把常量0放入棧 +--------+--------+ | local | stack | +-----------------+ | | 0 | +-----------------+ | | | +--------+--------+ 1 istore_1 //把棧頂的元素出棧,存到區域性變數表索引為1的位置 +--------+--------+ | local | stack | +-----------------+ | 0 | | +-----------------+ | | | +--------+--------+ 2 goto 8 //跳轉到第8條指令 8 iload_1 //把區域性變數表中索引為1的變數入棧 +--------+--------+ | local | stack | +-----------------+ | 0 | 0 | +-----------------+ | | | +--------+--------+ 9 bipush 100 //把100入棧 +--------+--------+ | local | stack | +-----------------+ | 0 | 0 | +-----------------+ | | 100 | +--------+--------+ 11 if_icmplt 5 //出棧兩個元素v1,v2,比較它們的值,當且僅當v1 < v2,跳轉到指令5 +--------+--------+ | local | stack | +-----------------+ | 0 | | +-----------------+ | | | +--------+--------+ 5 iinc 1 1 //自增區域性變數表中索引為1的值 +--------+--------+ | local | stack | +-----------------+ | 1 | | +-----------------+ | | | +--------+--------+ //進行下次迴圈直到指令11不滿足,到達指令14 14 return //清空棧,執行引擎把控制權交換給呼叫者。 +--------+--------+ | local | stack | +-----------------+ | 100 | | +-----------------+ | | | +--------+--------+
以上就是for
迴圈位元組碼執行的過程。可以發現,所有指令都是圍繞者區域性變數表和運算元棧在操作。
解惑
指令iconst_0
,iload_1
的命名解讀
第一個i
代表這是對int資料型別進行的操作
const
,load
是操作碼
0
,1
是隱含的運算元
上面的兩個指令等價於iconst 0
,iload 1
詳細的位元組碼解釋查閱《JVM 虛擬機器規範》
try-catch-finally 位元組碼分析
static int inc(){ int x; try { x = 1; return x; } catch (Exception e){ x = 2; return x; } finally { x = 3; } }
下面是它的位元組碼,這次我就不畫圖了,裡面的命令跟上面的類似。
static int inc(); descriptor: ()I flags: ACC_STATIC Code: stack=1, locals=4, args_size=0 0: iconst_1 //try 塊中的 x = 1; 1: istore_0 //儲存棧頂元素到區域性變數表中索引為 0 的 slot 中 2: iload_0 //載入區域性變數表中索引為 0 的值到棧中 3: istore_1 //儲存棧頂元素到區域性變數表中索引為 1 的 slot 中 4: iconst_3 //finally 塊中的 x = 3; 5: istore_0 //儲存棧頂元素到區域性變數表中索引為 0 的 slot 中,x 的值存在這裡。 6: iload_1 //載入區域性變數表中索引為 1 的值到棧中 7: ireturn //返回棧頂元素,即 x = 1;正常情況下函式執行到這裡就結束了,如果出現異常根據異常表跳轉到指定的位置 8: astore_1 //給 catch 塊中定義的 Exception e 賦值,儲存在 slot1 中。 9: iconst_2 //catch 塊中的 x = 2; 10: istore_0 11: iload_0 12: istore_2 13: iconst_3 //finally 塊中的 x = 3; 14: istore_0 15: iload_2 16: ireturn //此時返回的是 slot2 中的值,即 x = 2 17: astore_3 //如果出現不屬於 java.lang.Exception 及其子類的異常,才會根據異常表中的規則跳轉到這裡。 18: iconst_3 //finally 塊中的 x = 3; 19: istore_0 20: aload_3 //將異常載入到棧頂, 21: athrow //丟擲棧頂的異常 Exception table: from to target type 0 4 8 Class java/lang/Exception 0 4 17 any 8 13 17 any
位元組碼中 0 ~ 4 行將整數 1 賦值為變數 x,x 儲存在 slot0 中,並且將 x 的值拷貝一份放到 slot1。如果沒有出現異常,繼續走到 5 ~ 7 行,將 x 賦值為 3,然後讀取 slot1 中的值到棧頂,最後ireturn
返回棧頂的值,方法結束。
如果出現異常,PC 暫存器指標轉到第 8 行,第 8 ~ 16 行所做的事情就是將 2 賦值給 x,然後儲存 x 的拷貝,最後將 x 賦值為 3。方法返回前將 x 的拷貝 2 讀取到棧頂。
如果在 0 ~ 4,8 ~ 13 行中出現其他異常,則跳轉到第 17 行執行,先同樣執行finally
塊中的x = 3
,最後丟擲異常,方法結束。
可以看到,Java 的異常處理是通過異常表的方式來決定程式碼執行的路徑。而finally
的實現是通過在每個路徑的最後加入finally
塊中的位元組碼實現的。
參考資料
《Java 虛擬機器規範》、《深入理解JVM虛擬機器》
相關文章
- 例項分析理解Java位元組碼Java
- 【Java】JVM位元組碼分析JavaJVM
- 深入理解JVM位元組碼執行引擎JVM
- 一夜搞懂 | JVM 位元組碼執行引擎JVM
- 要點提煉| 理解JVM之位元組碼執行引擎JVM
- JVM(三):深入分析Java位元組碼-上JVMJava
- JVM(四):深入分析Java位元組碼-下JVMJava
- JVM學習筆記(四)—— 虛擬機器位元組碼執行引擎JVM筆記虛擬機
- 【JVM原始碼解析】模板直譯器解釋執行Java位元組碼指令(上)JVM原始碼Java
- 深入瞭解jvm-2Edition-虛擬機器位元組碼執行引擎JVM虛擬機
- Clojure 執行原理之位元組碼生成篇
- 虛擬機器位元組碼執行引擎虛擬機
- JVM 層對 jar 包位元組碼加密JVMJAR加密
- 玩命學JVM(一)—認識JVM和位元組碼檔案JVM
- 匹配雙位元組字元的正規表示式程式碼例項字元
- jvm位元組碼和類載入機制JVM
- jvm 虛擬機器位元組碼指令表JVM虛擬機
- 深入淺出JVM(十)之位元組碼指令(下篇)JVM
- JVM指令分析例項四(陣列、switch)JVM陣列
- 位元組碼底層分析String
- [深入理解Java虛擬機器]第八章 位元組碼執行引擎-基於棧的位元組碼解釋執行引擎Java虛擬機
- Nginx %00空位元組執行php漏洞NginxPHP
- JVM 內部原理(二)— 基本概念之位元組碼JVM
- 深入分析JVM執行引擎JVM
- 位元組碼指令分析 ++i 和 i++
- JVM 內部原理(六)— Java 位元組碼基礎之一JVMJava
- JVM 內部原理(七)— Java 位元組碼基礎之二JVMJava
- JVM模板直譯器:位元組碼的resolve過程JVM
- Java 位元組碼Java
- 位元組碼指令
- java多執行緒例項Java執行緒
- JVM 模板直譯器之位元組碼的resolve過程JVM
- JVM 模板直譯器之如何根據位元組碼生成彙編碼?JVM
- js呼叫執行exe應用程式程式碼例項JS
- javascript動態建立並執行css程式碼例項JavaScriptCSS
- javascript測試程式碼的執行時間程式碼例項JavaScript
- Activiti的流程例項【ProcessInstance】與執行例項【Execution】
- Java HashMap例項原始碼分析JavaHashMap原始碼