2025-05-23

異常表
每一個try語句塊catch的異常都與異常表中的一項相對應,異常表中的每一項都包括:


起點
終點,始終把catch異常位置的pc指針偏移量的最大值大1
 處理異常時跳轉到的字節碼序列中的pc指針偏移量
 被catch的異常類的常量池索引
 


例如:


public class Test {
    public static void main(String[] args) {


        try {
            Class.forName(“java.lang.String”);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }


    }
}


用javap –c查看字節碼如下:


 


Compiled from “Test.java”
public class Test extends java.lang.Object{
public Test();
  Code:
   0:    aload_0
   1:    invokespecial    #1; //Method java/lang/Object.”<init>”:()V
   4:    return


public static void main(java.lang.String[]);
  Code:
   0:    ldc    #2; //String java.lang.String
   2:    invokestatic    #3; //Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
   5:    pop   
   6:    goto    14
   9:    astore_1
   10:    aload_1
   11:    invokevirtual    #5; //Method java/lang/ClassNotFoundException.printStackTrace:()V
   14:    return
  Exception table:
   from   to  target type
     0     6     9   Class java/lang/ClassNotFoundException
}
可見ClassNotFoundException異常可能會在0~6之間拋出,9開始處的代碼處理此異常。


 


當產生異常的時候,jvm將會在整個異常表中搜索與之匹配的項,如果當前pc在異常表入口所指的范圍內,並且所拋出的異常是此入口所指向的類或者其子類,則跳轉到對應的處理代碼繼續執行。


方法可能會拋出哪些已檢查異常
Class文件的attribute_info中保存有Exceptions屬性,記錄著每個方法throws的異常信息。具體的可以查看class類文件格式相關的文章。


 


athrow指令從棧頂彈出Throwable對象引用,拋出異常。


 


finally語句
在jvm規范中,finally語句是通過jsr/jsr_w與ret指令實現的。當執行jsr/jsr_w的時候將finally執行完成後的返回地址壓入棧中,進入finally後會馬上將此地址保存到一個局部變量中,執行完成後,ret從此局部變量中取出返回地址。???為什麼會先把返回地址保存到局部變量中呢???因為,當從finally語句返回的時候需要將返回地址成棧中彈出,當finally語句非正常結束(break,continue,return, 拋異常)的時候就不用再考慮這個問題。


 


以下是jvm規范中Compiling finally的一段:


void tryFinally() {
    try {
        tryItOut();
    } finally {
        wrapItUp();
    }
}
the compiled code is
Method void tryFinally()
   0     aload_0            // Beginning of try block
   1    invokevirtual #6         // Method Example.tryItOut()V
   4     jsr 14            // Call finally block
   7     return            // End of try block
   8     astore_1            // Beginning of handler for any throw
   9     jsr 14            // Call finally block
  12     aload_1            // Push thrown value
  13     athrow            // and rethrow the value to the invoker
  14     astore_2            // Beginning of finally block
  15     aload_0            // Push this
  16     invokevirtual #5         // Method Example.wrapItUp()V
  19     ret 2            // Return from finally block
Exception table:
       From     To     Target         Type
    0        4        8           any
當tryItOut排除任何異常後都將會被異常表中的any項捕獲,執行完finally後,會執行athrow指令將異常拋出。


 


從jdk的某一個版本開始就不會編譯出編譯出含jsr/jsr_w、ret的字節碼瞭,因為有指令上的缺陷,導致jvm的檢驗和分析系統出現漏洞。


 


再說finally的非正常退出
在finally中使用break、continue、return、拋出異常等認為是finally的非正常結束。非正常結束的時候,ret指令不會被執行,很可能會出現意想不到的結果。如:


public class Test {
    public static boolean test(boolean b) {
        while (b) {
            try {
                return true;
            } finally {
                /**//*
                break;                          始終返回false
                continue;                         javac編譯再java執行會出現死循環
                                                在eclipse中甚至會出現報錯:提示找到不main class
                return false;                     始終返回false
                throw new RuntimeException(“”);    拋出異常
                 */
            }
        }


        return false;
    }


    public static void main(String[] args) {
 &nb

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *