字节码层面分析 try-catch-finally 中的 return 问题

结论

  1. finally 中的代码总会被执行(Java语言规范规定的)。
  2. 如果 trycatch 中有 return,那么:
    • finally 中无 return,当返回值的类型是引用类型(可变类)时,返回值会受到 finally 中代码的影响。
    • finally 中有 return,会直接在 finally 中退出,导致 trycatch 中的 return 失效。

测试代码

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

    }

    private static int testReturn1() {
        int i = 0;
        try {
            i++;
            return i; // 1
        } catch (Exception e) {
            i++;
        } finally {
            i++;
        }
        return i;
    }

    private Integer testReturn2() {
        Integer i = 0;
        try {
            i++;
            return i; // 2
        } catch (Exception e) {
            i++;
        } finally {
            i++;
        }
        return i;
    }

    private int testReturn3() {
        int i = 0;
        try {
            i++;
            int x = i / 0 ;
        } catch (Exception e) {
            i++;
            return i; // 2
        } finally {
            i++;
        }
        return i;
    }

    private int testReturn4() {
        int i = 0;
        try {
            i++;
            return i;
        } catch (Exception e) {
            i++;
            return i;
        } finally {
            i++;
            return i; // 2
        }
    }
}

执行一下代码以便生成 TestReturn.class

反编译

方法一:打开 cmd,并将窗口最大化(避免打印的反编译信息换行),进入 TestReturn.class 所在目录,执行命令 javap -v -p TestReturn 将字节码反编译成助记符

方法二:借助于 IDEA 插件 jclasslib 查看字节码。

基本类型

testReturn1() 方法为例来分析字节码,这里使用 jclasslib 查看字节码。

字节码

Code 中的字节码页签就是字节码反编译后的字节码行号和助记符。
字节码

异常表

如果2~7 行出现 Exception 则跳转到 12 行进行处理,否则跳转到 22 行进行处理。
异常表

行号表

Code 中的 LineNumberTable 记录的是 PC(程序计数器,也就是字节码行号指示器)对应的 Java 代码行号。
行号表

根据这个映射表,我们可以把字节码跟 Java 代码联系起来。
testReturn1

由上图可知,finally 一定会执行的语义是通过在 trycatch 后面添加 finally 的字节码实现的。

逐行分析

0 iconst_0

int 取值 -1~5 时,JVM 采用 iconst 指令将常量压入栈中。
iconst_0

1 istore_0

弹出栈顶元素,并保存到局部变量表的 0 号槽。
istore_0

2 iinc 0 by 1

0 号槽的变量加 1
iinc

5 iload_0

将局部变量表 0 号槽的变量入栈,作为暂定的返回值。
iload_0

6 istore_1

弹出栈顶元素,并保存到局部变量表的 1 号槽,即保存暂定的返回值。
istore_1

7 iinc 0 by 1

0 号槽的变量加 1
iinc 0 by 1

10 iload_1

将局部变量表 1 号槽的变量入栈。
iload_1

11 ireturn

返回栈顶的值。
对于本代码,执行到这里就结束了,总体流程为:
tryfinallytry 中的 retrun
ireturn

12 astore_1

将异常对象 e 保存到局部变量表的 1 号槽(覆盖掉暂定的返回值)。
由于2~7 行出现 Exception 都会跳转到 12 行进行处理,所以此时的状态不好确定,后面的就不分析了。

引用类型

这里直接贴出 testReturn2() 跟字节码关联后的图。
testReturn2

逐行分析

0 iconst_0

int 取值 -1~5 时,JVM 采用 iconst 指令将常量压入栈中。
image

1 invokestatic #3 <java/lang/Integer.valueOf>

调用编号为 3 的类方法,也就是 Integer.valueOf,将栈顶的基本类型转换为引用类型(以后缀 I 标识)。
invokestatic

4 astore_1

将栈顶的引用类型保存到局部变量表的 1 号槽。
astore_1

5 aload_1

将局部变量表 1 号槽的引用类型压入栈中。
image

6 astore_2

将栈顶的引用类型保存到局部变量表的 2 号槽。
注意:此时的两个引用指向同一个对象。
astore_2

7 aload_1

将局部变量表 1 号槽的引用类型压入栈中。
aload_1

8 invokevirtual #4 <java/lang/Integer.intValue>

调用编号为 4 的实例方法,也就是 Integer.intValue, 将栈顶的引用类型转换为基本类型。
invokevirtual

11 iconst_1

将常量 1 压入栈中。
iconst_1

12 iadd

弹出栈顶两 int 类型数,相加后结果入栈。
image

13 invokestatic #3 <java/lang/Integer.valueOf>

将栈顶的基本类型转换为引用类型。
invokestatic

16 dup

复制栈顶元素,并再次入栈,即此时栈中有两个相同的引用。
这两个引用与变量槽中的不同,用浅蓝色区分。
dup

17 astore_1

将栈顶的引用类型覆盖到局部变量表的 1 号槽。
astore_1

18 astore_3

将栈顶的引用类型保存到局部变量表的 3 号槽。
astore_3

19 aload_2

将局部变量表 2 号槽的引用类型压入栈中。
aload_2

20 pop

从栈顶弹出一个字长的元素,即弹出栈顶。
pop

21 aload_1

将局部变量表 1 号槽的引用类型压入栈中。
aload_1

22 astore_2

将栈顶的引用类型覆盖到局部变量表的 2 号槽。
astore_2

23 aload_1

将局部变量表 1 号槽的引用类型压入栈中,作为暂定的返回值。
aload_1

24 astore_3

将栈顶的引用类型覆盖到局部变量表的 3 号槽。
astore_3

25 aload_1

将局部变量表 1 号槽的引用类型压入栈中。
aload_1

26 invokevirtual #4 <java/lang/Integer.intValue>

调用编号为 4 的实例方法,也就是 Integer.intValue, 将栈顶的引用类型转换为基本类型。
invokevirtual

29 iconst_1

将常量 1 压入栈中。
iconst_1

30 iadd

弹出栈顶两 int 类型数,相加后结果入栈。
iadd

31 invokestatic #3 <java/lang/Integer.valueOf>

将栈顶的基本类型转换为引用类型(以绿色标识)。
invokestatic

34 dup

复制栈顶元素,并再次入栈,即此时栈中有两个相同的引用。
dup

35 astore_1

将栈顶的引用类型覆盖到局部变量表的 1 号槽。
astore_1

36 astore 4

将栈顶的引用类型保存到局部变量表的 4 号槽。
astore

38 aload_3

将局部变量表 3 号槽的引用类型压入栈中。
aload_3

39 pop

从栈顶弹出一个字长的元素,即弹出栈顶。
pop

40 aload_2

将局部变量表 2 号槽的引用类型压入栈中。
aload_2

41 areturn

返回栈顶引用,这样返回的就是 1

posted @ 2021-06-27 23:17  ageovb  阅读(257)  评论(0编辑  收藏  举报