從JVM指令層面看try-catch-finally返回值問題

貌似很多人對下面的方法的返回值都比較迷糊:

Java代碼  收藏代碼

  1. package cc.lixiaohui.demo;    

  2.     

  3. public class ReturnValueTest {    

  4.     public int test() {    

  5.         int a;    

  6.         try {    

  7.             a = 1;    

  8.             //int b = 1 / 0;    

  9.             return a;    

  10.         } catch (Exception e) {    

  11.             a = 2;    

  12.             return a;    

  13.         } finally {    

  14.             a = 3;    

  15.         }    

  16.     }    

  17. }  

 

test方法的返回值自然是1,如果把註釋那行去掉,那就是2.

 

爲什麼?

用javap -verbose ReturnValueTest 查看字節碼:

重點查看test()方法指令:下載

Javap代碼  收藏代碼

  1. Compiled from "ReturnValueTest.java"  

  2. public class cc.lixiaohui.demo.ReturnValueTest extends java.lang.Object  

  3.   SourceFile: "ReturnValueTest.java"  

  4.   minor version: 0  

  5.   major version: 49       

  6.   Constant pool:        --常量池  

  7. const #1 = class        #2;     //  cc/lixiaohui/demo/ReturnValueTest  

  8. const #2 = Asciz        cc/lixiaohui/demo/ReturnValueTest;  

  9. const #3 = class        #4;     //  java/lang/Object  

  10. const #4 = Asciz        java/lang/Object;  

  11. const #5 = Asciz        <init>;  

  12. const #6 = Asciz        ()V;  

  13. const #7 = Asciz        Code;  

  14. const #8 = Method       #3.#9;  //  java/lang/Object."<init>":()V  

  15. const #9 = NameAndType  #5:#6;//  "<init>":()V  

  16. const #10 = Asciz       LineNumberTable;  

  17. const #11 = Asciz       LocalVariableTable;  

  18. const #12 = Asciz       this;  

  19. const #13 = Asciz       Lcc/lixiaohui/demo/ReturnValueTest;;  

  20. const #14 = Asciz       test;  

  21. const #15 = Asciz       ()I;  

  22. const #16 = class       #17;    //  java/lang/Exception  

  23. const #17 = Asciz       java/lang/Exception;  

  24. const #18 = Asciz       a;  

  25. const #19 = Asciz       I;  

  26. const #20 = Asciz       e;  

  27. const #21 = Asciz       Ljava/lang/Exception;;  

  28. const #22 = Asciz       SourceFile;  

  29. const #23 = Asciz       ReturnValueTest.java;  

  30.   

  31. {  

  32. public cc.lixiaohui.demo.ReturnValueTest();     --構造方法就不分析了  

  33.   Code:  

  34.    Stack=1, Locals=1, Args_size=1  

  35.    0:   aload_0  

  36.    1:   invokespecial   #8; //Method java/lang/Object."<init>":()V  

  37.    4:   return  

  38.   LineNumberTable:  

  39.    line 80  

  40.   

  41.   LocalVariableTable:  

  42.    Start  Length  Slot  Name   Signature  

  43.    0      5      0    this       Lcc/lixiaohui/demo/ReturnValueTest;  

  44.   

  45.   

  46. public int test();  

  47.   Code:  

  48.    Stack=1, Locals=5, Args_size=1 -- [Stack=1貌似表示棧深度爲1(不確定),]Locals=5表示局部變量表長度爲5, Args_size=1表示該方法有1個參數(this)  

  49.    0:   iconst_1        --將int值 1 壓棧  

  50.    1:   istore_1        --將棧頂值(即1)彈出保存至局部變量表第2個位置(局部變量表下標是從0開始的,但是0位置被this變量佔用了)  

  51.    2:   iload_1         --將局部變量表第2個位置的值壓棧  

  52.    3:   istore  4       --將棧頂的值彈出並保存至局部變量表第5個位置(這裏可以看到)  

  53.    5:   iconst_3        --(這裏開始爲finally塊)將int值3壓棧  

  54.    6:   istore_1        --將棧頂的值(即3)彈出並存儲至局部變量表第2個位置  

  55.    7:   iload   4       --將局部變量表第5個位置(即1)壓棧  

  56.    9:   ireturn         --返回棧頂的值(即1), 結束方法調用(該路徑爲try(未拋異常) -> finally)  

  57.      

  58.    10:  astore_2        --將棧頂的引用(這裏即爲catch塊中捕捉到的異常e)存儲至局部變量表的第3個位置  

  59.    11:  iconst_2        --將int值2壓棧  

  60.    12:  istore_1        --將棧頂值(即2)彈出並存儲至局部變量表第2個位置  

  61.    13:  iload_1         --將局部變量表第2個位置的值(即2)壓棧  

  62.    14:  istore  4       --將棧頂值彈出(即2)並保存至局部變量表第5個位置,原來第五個位置是1,現在1被覆蓋了,變爲2  

  63.    16:  iconst_3        --(這裏開始爲finally塊)將int值3壓棧  

  64.    17:  istore_1        --將棧頂的值(即3)彈出並存儲至局部變量表第2個位置  

  65.    18:  iload   4       --將局部變量表第5個位置(即2)壓棧  

  66.    20:  ireturn         --返回棧頂的值(即2),結束方法調用(該路徑爲try(拋Exception或其子類異常) -> catch -> finally)  

  67.      

  68.    21:  astore_3        --將棧頂的引用(這裏爲非Exception的子類異常)存儲至局部變量表的第4個位置  

  69.    22:  iconst_3        --將int值3壓棧  

  70.    23:  istore_1        --將棧頂值(即3)彈出並存儲至局部變量表第二個位置  

  71.    24:  aload_3         --將局部變量表第4個位置(即爲異常引用)壓棧  

  72.    25:  athrow          --將棧頂的異常拋出(該路徑爲try(拋非Exception或其子類異常) -> finally, 或者try(拋Exception或其子類異常) -> catch(拋任何異常) -> finally )  

  73.   Exception table:  

  74.    from   to  target type  

  75.      0     5    10   Class java/lang/Exception  --若執行到0-5行(即在try塊中)拋出java.lang.Exception或其子類則跳轉至第10行執行(即catch塊)  

  76.      0     5    21   any                        --若執行到0-5行(即在try塊中)拋出非java.lang.Exception或其子類則跳轉至第21行執行(即finally塊)  

  77.     10    16    21   any                        --若執行到10-16行(即在catch塊中)拋出任何異常則跳轉至21行執行(即finally塊)  

  78.   LineNumberTable:      --這個表爲源碼行數與字節碼行數的映射  

  79.    line 130  

  80.    line 142  

  81.    line 195  

  82.    line 147  

  83.    line 1510  

  84.    line 1611  

  85.    line 1713  

  86.    line 1916  

  87.    line 1718  

  88.    line 1821  

  89.    line 1922  

  90.    line 2024  

  91.   

  92.   LocalVariableTable:       --這個即爲局部變量表, start和length結合起來就可以確定該變量的作用範圍, 例如this作用範圍爲整個方法  

  93.    Start  Length  Slot  Name   Signature  

  94.    0      26      0    this       Lcc/lixiaohui/demo/ReturnValueTest;   --佔用第1個slot(一個slot應該是32bits)  

  95.    2      8      1    a       I                                         --I標識int, 佔用第2個slot  

  96.    13      8      1    a       I                                        --佔用第2個slot  

  97.    24      2      1    a       I                                        --佔用第2個slot  

  98.    11      10      2    e       Ljava/lang/Exception;                   --佔用第3個slot  

  99.   

  100.   

  101. }  

 

可以發現jvm始終把返回值放在最後一個局部變量表的位置,而且在finally中改變x並不影響返回值.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章