返回 登录
0

从JVM指令层面看try-catch-finally返回值问题

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

package cc.lixiaohui.demo;  

public class ReturnValueTest {  
    public int test() {  
        int a;  
        try {  
            a = 1;  
            //int b = 1 / 0;  
            return a;  
        } catch (Exception e) {  
            a = 2;  
            return a;  
        } finally {  
            a = 3;  
        }  
    }  
}

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

为什么?
用javap -verbose ReturnValueTest 查看字节码:
重点查看test()方法指令:

Compiled from "ReturnValueTest.java"
public class cc.lixiaohui.demo.ReturnValueTest extends java.lang.Object
  SourceFile: "ReturnValueTest.java"
  minor version: 0
  major version: 49     
  Constant pool:        --常量池
const #1 = class        #2;     //  cc/lixiaohui/demo/ReturnValueTest
const #2 = Asciz        cc/lixiaohui/demo/ReturnValueTest;
const #3 = class        #4;     //  java/lang/Object
const #4 = Asciz        java/lang/Object;
const #5 = Asciz        <init>;
const #6 = Asciz        ()V;
const #7 = Asciz        Code;
const #8 = Method       #3.#9;  //  java/lang/Object."<init>":()V
const #9 = NameAndType  #5:#6;//  "<init>":()V
const #10 = Asciz       LineNumberTable;
const #11 = Asciz       LocalVariableTable;
const #12 = Asciz       this;
const #13 = Asciz       Lcc/lixiaohui/demo/ReturnValueTest;;
const #14 = Asciz       test;
const #15 = Asciz       ()I;
const #16 = class       #17;    //  java/lang/Exception
const #17 = Asciz       java/lang/Exception;
const #18 = Asciz       a;
const #19 = Asciz       I;
const #20 = Asciz       e;
const #21 = Asciz       Ljava/lang/Exception;;
const #22 = Asciz       SourceFile;
const #23 = Asciz       ReturnValueTest.java;

{
public cc.lixiaohui.demo.ReturnValueTest();     --构造方法就不分析了
  Code:
   Stack=1, Locals=1, Args_size=1
   0:   aload_0
   1:   invokespecial   #8; //Method java/lang/Object."<init>":()V
   4:   return
  LineNumberTable:
   line 8: 0

  LocalVariableTable:
   Start  Length  Slot  Name   Signature
   0      5      0    this       Lcc/lixiaohui/demo/ReturnValueTest;


public int test();
  Code:
   Stack=1, Locals=5, Args_size=1 -- [Stack=1貌似表示栈深度为1(不确定),]Locals=5表示局部变量表长度为5, Args_size=1表示该方法有1个参数(this)
   0:   iconst_1        --将int1 压栈
   1:   istore_1        --将栈顶值(即1)弹出保存至局部变量表第2个位置(局部变量表下标是从0开始的,但是0位置被this变量占用了)
   2:   iload_1         --将局部变量表第2个位置的值压栈
   3:   istore  4       --将栈顶的值弹出并保存至局部变量表第5个位置(这里可以看到)
   5:   iconst_3        --(这里开始为finally块)将int3压栈
   6:   istore_1        --将栈顶的值(即3)弹出并存储至局部变量表第2个位置
   7:   iload   4       --将局部变量表第5个位置(即1)压栈
   9:   ireturn         --返回栈顶的值(即1), 结束方法调用(该路径为try(未抛异常) -> finally)

   10:  astore_2        --将栈顶的引用(这里即为catch块中捕捉到的异常e)存储至局部变量表的第3个位置
   11:  iconst_2        --将int2压栈
   12:  istore_1        --将栈顶值(即2)弹出并存储至局部变量表第2个位置
   13:  iload_1         --将局部变量表第2个位置的值(即2)压栈
   14:  istore  4       --将栈顶值弹出(即2)并保存至局部变量表第5个位置,原来第五个位置是1,现在1被覆盖了,变为2
   16:  iconst_3        --(这里开始为finally块)将int3压栈
   17:  istore_1        --将栈顶的值(即3)弹出并存储至局部变量表第2个位置
   18:  iload   4       --将局部变量表第5个位置(即2)压栈
   20:  ireturn         --返回栈顶的值(即2),结束方法调用(该路径为try(抛Exception或其子类异常) -> catch -> finally)

   21:  astore_3        --将栈顶的引用(这里为非Exception的子类异常)存储至局部变量表的第4个位置
   22:  iconst_3        --将int3压栈
   23:  istore_1        --将栈顶值(即3)弹出并存储至局部变量表第二个位置
   24:  aload_3         --将局部变量表第4个位置(即为异常引用)压栈
   25:  athrow          --将栈顶的异常抛出(该路径为try(抛非Exception或其子类异常) -> finally, 或者try(抛Exception或其子类异常) -> catch(抛任何异常) -> finally )
  Exception table:
   from   to  target type
     0     5    10   Class java/lang/Exception  --若执行到0-5行(即在try块中)抛出java.lang.Exception或其子类则跳转至第10行执行(即catch块)
     0     5    21   any                        --若执行到0-5行(即在try块中)抛出非java.lang.Exception或其子类则跳转至第21行执行(即finally块)
    10    16    21   any                        --若执行到10-16行(即在catch块中)抛出任何异常则跳转至21行执行(即finally块)
  LineNumberTable:      --这个表为源码行数与字节码行数的映射
   line 13: 0
   line 14: 2
   line 19: 5
   line 14: 7
   line 15: 10
   line 16: 11
   line 17: 13
   line 19: 16
   line 17: 18
   line 18: 21
   line 19: 22
   line 20: 24

  LocalVariableTable:       --这个即为局部变量表, start和length结合起来就可以确定该变量的作用范围, 例如this作用范围为整个方法
   Start  Length  Slot  Name   Signature
   0      26      0    this       Lcc/lixiaohui/demo/ReturnValueTest;   --占用第1个slot(一个slot应该是32bits)
   2      8      1    a       I                                         --I标识int, 占用第2个slot
   13      8      1    a       I                                        --占用第2个slot
   24      2      1    a       I                                        --占用第2个slot
   11      10      2    e       Ljava/lang/Exception;                   --占用第3个slot


}

可以发现jvm始终把返回值放在最后一个局部变量表的位置,而且在finally中改变x并不影响返回值。

学习Java的同学注意了!!!
学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群,群号码:454297367 我们一起学Java!

评论