从字节码看try catch finally的return如何执行

时间秒杀一切 提交于 2020-01-16 20:57:44

文章是对两位博主的总结,提炼,原文如下链接:
从字节码看try catch finally的return如何执行
Java中try catch finally语句中含有return语句的执行情况(总结版)

测试代码很简单,如下:
Test.java

public class Test {
    public int get() {
        try{
            return 0;
        } catch (Exception e) {
            e.printStackTrace();
            return 1;
        } finally {
            return 2;
        }
    }
}

执行$ javap -verbose Test.class

$ javap -verbose Test.class
Classfile /E:/workspace/java/Test.class
  Last modified 2018-1-29; size 405 bytes
  MD5 checksum f8f6002de3931b2e95125679f2ce1f6c
  Compiled from "Test.java"
public class Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#17         // java/lang/Object."<init>":()V
   #2 = Class              #18            // java/lang/Exception
   #3 = Methodref          #2.#19         // java/lang/Exception.printStackTrace                          :()V
   #4 = Class              #20            // Test
   #5 = Class              #21            // java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               get
  #11 = Utf8               ()I
  #12 = Utf8               StackMapTable
  #13 = Class              #18            // java/lang/Exception
  #14 = Class              #22            // java/lang/Throwable
  #15 = Utf8               SourceFile
  #16 = Utf8               Test.java
  #17 = NameAndType        #6:#7          // "<init>":()V
  #18 = Utf8               java/lang/Exception
  #19 = NameAndType        #23:#7         // printStackTrace:()V
  #20 = Utf8               Test
  #21 = Utf8               java/lang/Object
  #22 = Utf8               java/lang/Throwable
  #23 = Utf8               printStackTrace
{
  public Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>                          ":()V
         4: return
      LineNumberTable:
        line 1: 0

  public int get();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=4, args_size=1
         0: iconst_0
         1: istore_1
         2: iconst_2
         3: ireturn
         4: astore_1
         5: aload_1
         6: invokevirtual #3                  // Method java/lang/Exception.prin                          tStackTrace:()V
         9: iconst_1
        10: istore_2
        11: iconst_2
        12: ireturn
        13: astore_3
        14: iconst_2
        15: ireturn
      Exception table:
         from    to  target type
             0     2     4   Class java/lang/Exception
             0     2    13   any
             4    11    13   any
      LineNumberTable:
        line 4: 0
        line 9: 2
        line 5: 4
        line 6: 5
        line 7: 9
        line 9: 11
      StackMapTable: number_of_entries = 2
        frame_type = 68 /* same_locals_1_stack_item */
          stack = [ class java/lang/Exception ]
        frame_type = 72 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]
}
SourceFile: "Test.java"

直接看重点 Exception table,字节码其他内容不做解释,想了解请移步Class文件格式,并参考JVM字节码手册

      Exception table:
         from    to  target type
             0     2     4   Class java/lang/Exception
             0     2    13   any
             4    11    13   any

JVM8虚拟机规范中的Code属性的标准结构如下:

    {   u2 start_pc;
        u2 end_pc;
        u2 handler_pc;
        u2 catch_type;
    } exception_table[exception_table_length];

看代码就已经比较好理解了:从start_pc(开始的pc指针)执行到end_pc(结束的pc指针),假如发生了catch_type类型的异常,就跳转到异常处理的pc指针处执行(handler_pc)
从0到2执行,要是有java/lang/Exception异常,就跳转到target(目标)4行执行;假如是其他的异常(any),就跳到13行执行;同时发生java/lang/Exception异常后,执行4-11行时假如又发生任意异常(any),就跳到13行执行。
jvm虚拟机就是这样通过异常表来执行的。

因此上述字节码中,有三段重复的字节码执行,带有3个return指令,第一个是没有异常情况下执行的,第二个是我们遇到在catch代码块中指定的异常情况下执行的,第三个是遇到我们未指定的异常情况下执行的。

总结一下,在编译时候,由于JVM没有那么智能,不能像人一样识别finally,因此我们把finally中的代码块放到了try语句块的return之前,并且把try代码块中return的value保存下来,之后根据以下情况(下一段落)来做处理,所以如果未发生异常,那么字节码会顺序执行。如果有异常,那么根据这个异常是否属于在catch中,跳转执行不同的字节码段

try catch finally和return其他状况总结如下:

try语句在返回前,将其他所有的操作执行完,保留好要返回的值,而后转入执行finally中的语句,而后分为以下三种情况:

情况一:如果finally中有return语句,则会将try中的return语句”覆盖“掉,直接执行finally中的return语句,得到返回值,这样便无法得到try之前保留好的返回值。

情况二:如果finally中没有return语句,也没有改变要返回值,则执行完finally中的语句后,会接着执行try中的return语句,返回之前保留的值。

情况三:如果finally中没有return语句,但是改变了要返回的值,这里有点类似与引用传递和值传递的区别,分以下两种情况,:

1)如果return的数据是基本数据类型或文本字符串,则在finally中对该基本数据的改变不起作用,try中的return语句依然会返回进入finally块之前保留的值。

2)如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!