Java中的異常表(Exception table)

Java 代碼中通過使用 try-catch-finally 塊來對異常進行捕獲/處理。但是對于 JVM 來說,是如何處理 try/catch 代碼塊與異常的呢?
實際上 Java代碼在進行編譯時,編譯器會在代碼后附加一個異常表,以實現try塊出現異常后能進入對應的異常處理程序執行。

  • 如果在方法執行期間拋出異常,Java 虛擬機會在異常表中搜索匹配的條目。
  • 如果當前PC程序計數器在條目指定的范圍內,并且拋出的異常類是條目指定的異常類(或者是指定異常類的子類),則異常表條目匹配。
  • Java 虛擬機按照條目在表中出現的順序搜索異常表。當找到第一個匹配項時,Java 虛擬機將程序計數器設置為新的 pc 偏移位置并在那里繼續執行。如果未找到匹配項,Java 虛擬機將彈出當前堆棧幀并重新拋出相同的異常。

JVM對異常表的約定

在 JVM 規范中,對 Exception table 有以下幾個約定:
1.Exception table 的結構:
在 JVM 規范中,Exception table 被定義為一張表格,由多行記錄組成。每一行記錄用于描述一個代碼塊,其中包含了代碼塊的起始地址、結束地址、異常處理程序的程序計數器(PC)值以及異常類類型。

2.Exception table 的字節碼偏移值:
在 JVM 規范中,Exception table 中的每個記錄都包含了字節碼偏移值(Bytecode offset)和行號信息,這些信息用于告訴 JVM 在哪個字節碼偏移值處發生了異常。這一信息在調試 Java 代碼時十分有用。

3.Exception table 列表的順序:
在 JVM 規范中,Exception table 中的代碼塊記錄必須按照字節碼地址從低到高排序。這個順序確保了在 JVM 查找代碼塊和異常處理程序時能夠正確有效。

4.Exception table 的匹配邏輯:
在 JVM 規范中,當 JVM 發生異常時,會遍歷 Exception table 列表,按行依次匹配當前 PC 計數器和代碼塊的起始和結束字節碼偏移值,以確定當前發生異常所在的代碼塊和異常處理程序。

Exception table 是 JVM 中非常重要的數據結構之一,為 JVM 提供了一種有效、可靠的異常處理機制。在 Java 編程中,Exception table 可以幫助開發者更好更快地調試和解決問題,保證 Java 程序的可靠性和穩定性。

模擬JVM的執行過程

class Ball extends Exception {
}
class Pitcher {
    private static Ball ball = new Ball();
    static void playBall() {
        int i = 0;
        while (true) {
            try {
                if (i % 4 == 3) {
                    throw ball;
                }
                ++i;
            }
            catch (Ball b) {
                i = 0;
            }
        }
    }
}

編譯后通過javap -v進行反編譯,找到playBall Java方法對應的Code指令:

   0 iconst_0             // Push constant 0
   1 istore_0             // Pop into local var 0: int i = 0;
                          // The try block starts here (see exception table, below).
   2 iload_0              // Push local var 0
   3 iconst_4             // Push constant 4
   4 irem                 // Calc remainder of top two operands
   5 iconst_3             // Push constant 3
   6 if_icmpne 13         // Jump if remainder not equal to 3: if (i % 4 == 3) {
                          // Push the static field at constant pool location #5,
                          // which is the Ball exception itching to be thrown
   9 getstatic #5 <Field Pitcher.ball LBall;>
  12 athrow               // Heave it home: throw ball;
  13 iinc 0 1             // Increment the int at local var 0 by 1: ++i;
                          // The try block ends here (see exception table, below).
  16 goto 2               // jump always back to 2: while (true) {}
                          // The following bytecodes implement the catch clause:
  19 pop                  // Pop the exception reference because it is unused
  20 iconst_0             // Push constant 0
  21 istore_0             // Pop into local var 0: i = 0;
  22 goto 2               // Jump always back to 2: while (true) {}
Exception table:
   from   to  target type
     2    16    19   <Class Ball>

對于每個 catch 塊捕獲的異常,異常表都有一個條目。每個條目有四個信息:

  • from:可能發生異常的起始點指令索引下標(包含)
  • to:可能發生異常的結束點指令索引下標(不包含)
  • target:在from和to的范圍內,發生異常后,開始處理異常的指令索引下標
  • type:當前范圍可以處理的異常類信息

基于異常表條目,可以判斷出

  • try塊,對應PC偏移范圍的 2~15
  • catch塊,對應PC偏移范圍的 19~21

異常表中,覆蓋范圍區間是左開右閉 [from, to),為什么沒有包含右邊界,這個就有點意思了

The fact that end_pc is exclusive is a historical mistake in the design of the Java Virtual Machine: if the Java Virtual Machine code for a method is exactly 65535 bytes long and ends with an instruction that is 1 byte long, then that instruction cannot be protected by an exception handler. A compiler writer can work around this bug by limiting the maximum size of the generated Java Virtual Machine code for any method, instance initialization method, or static initializer (the size of any code array) to 65534 bytes.
start_pc、end_pc 是一對參數,對應的是 Exception table 里面的 from 和 to,表示異常的覆蓋范圍。

不包含 end_pc 是 JVM 設計過程中的一個歷史性的錯誤。

在Java中,一個方法的長度從字節碼層面來說是有限制的。具體來說,Java虛擬機規范定義了一個方法的字節碼長度不能超過65535個字節,也就是64KB。
這個限制是由于Java虛擬機規范中方法表結構的設計所決定的。方法表結構中有一個字段code_length用于表示方法的字節碼長度,這個字段是一個16位的無符號整數,因此最大值為65535。

因為如果 JVM 中一個方法編譯后的代碼正好是 65535 字節長,并且以一條 1 字節長的指令結束,那么該指令就不能被異常處理機制所保護。存在邊界問題,因此異常的覆蓋范圍定為左開右閉。

示例中,Pitcher#playball方法會一直循環;每經過四次循環,playball 就會拋出Ball并catch住。
因為 try 塊和 catch 子句都在while(true) 循環中,所以永遠不會停止。

局部變量i從 0 開始,每次循環遞增。當if語句為 時true,即每次i等于 3 時都會拋出異常。
Java 虛擬機檢查異常表,發現確實有匹配的條目。條目的有效范圍是從 2 到 15,包括了在 pc 偏移量 12 處拋出異常。條目對應能處理的異常類型class 是Ball,拋出異常的 class 也是Ball。鑒于這種完美匹配,Java 虛擬機將拋出的異常對象壓入堆棧,并在 pc 偏移量 19 處繼續執行。
catch 子句只是將int i重置為 0,然后循環重新開始。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容