异常处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class Main { public static void main (String[] args) { try { test1(); } catch (IndexOutOfBoundsException e) { test2(); } catch (Exception e) { test2(); } finally { test3(); } } private static final void test1 () { } private static final void test2 () { } private static final void test3 () { } }
生成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=3, args_size=1 0: invokestatic #2 // Method test1:()V 3: invokestatic #3 // Method test3:()V 6: goto 35 9: astore_1 10: invokestatic #5 // Method test2:()V 13: invokestatic #3 // Method test3:()V 16: goto 35 19: astore_1 20: invokestatic #5 // Method test2:()V 23: invokestatic #3 // Method test3:()V 26: goto 35 29: astore_2 30: invokestatic #3 // Method test3:()V 33: aload_2 34: athrow 35: return Exception table: from to target type 0 3 9 Class java/lang/IndexOutOfBoundsException 0 3 19 Class java/lang/Exception 0 3 29 any 9 13 29 any 19 23 29 any LocalVariableTable: Start Length Slot Name Signature 10 3 1 e Ljava/lang/IndexOutOfBoundsException; 20 3 1 e Ljava/lang/Exception; 0 36 0 args [Ljava/lang/String; StackMapTable: number_of_entries = 4 frame_type = 73 /* same_locals_1_stack_item */ stack = [ class java/lang/IndexOutOfBoundsException ] frame_type = 73 /* same_locals_1_stack_item */ stack = [ class java/lang/Exception ] frame_type = 73 /* same_locals_1_stack_item */ stack = [ class java/lang/Throwable ] frame_type = 5 /* same */
可以看出多了这些东西 goto
指令,athrow
指令, Exception table
和 StackMapTable
, 并且局部变量表里面增加了一条记录.
goto
: 用于跳转
athrow
: 通过 athrow
可以知道局部变量表中位置为 2 的变量是一个异常
Exception table
: 中的from和to限定了异常的处理范围, target表示跳转的位置, type表示可以捕获的异常类型
StackMapTable
: 为了提高JVM在类型检查的验证过程的效率, 在字节码规范中添加了Stack Map Table属性, number_of_entries
代表了栈图frame的个数
finally中执行了test3()
方法, 可以看到之所以finally可以保证执行, 是因为finally框住的逻辑被拷贝了几份, 分别放在try
和catch
代码块下面, 伪代码是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 public static void main (String[] args) { try { test1(); test3(); } catch (IndexOutOfBoundsException e) { test2(); test3(); } catch (Exception e) { test2(); test3(); } }
有返回值的情况 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class Main { public static void main (String[] args) { } public int test () { try { return test1(); } catch (Exception e) { return test2(); } finally { return test3(); } } private int test1 () { return 1 ; } private int test2 () { return 2 ; } private int test3 () { return 3 ; } }
运行结果为3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public int test(); descriptor: ()I flags: ACC_PUBLIC Code: stack=1, locals=4, args_size=1 0: aload_0 1: invokespecial #2 // Method test1:()I 4: istore_1 5: aload_0 6: invokespecial #3 // Method test3:()I 9: ireturn 10: astore_1 11: aload_0 12: invokespecial #5 // Method test2:()I 15: istore_2 16: aload_0 17: invokespecial #3 // Method test3:()I 20: ireturn 21: astore_3 22: aload_0 23: invokespecial #3 // Method test3:()I 26: ireturn
从字节码上看就能知道, finally 在任何情况下都会执行, 所以return test3();
必定会被执行, 所以一定返回了3
.
另外一种有返回值的情况 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Main { public static void main (String[] args) { Main m = new Main (); System.out.println(m.test()); } public int test () { int i = 0 ; try { return i; } catch (Exception e) { return i; } finally { i = ++i; } } }
结果为:0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public int test(); descriptor: ()I flags: ACC_PUBLIC Code: stack=1, locals=5, args_size=1 0: iconst_0 1: istore_1 2: iload_1 3: istore_2 4: iinc 1, 1 7: iload_1 8: istore_1 9: iload_2 10: ireturn 11: astore_2 12: iload_1 13: istore_3 14: iinc 1, 1 17: iload_1 18: istore_1 19: iload_3 20: ireturn 21: astore 4 23: iinc 1, 1 26: iload_1 27: istore_1 28: aload 4 30: athrow
为什么finally一定会执行, 但是结果缺不是i = i + 1
只后的1
, 而是0
呢? 从字节码上同样可以看出原因.
局部变量表第一个位置是this (this,null,null,exception)
局部变量表第二个位置写入0 (this,0,null,exception)
加载局部变量表第二个位置的值到操作数栈, [0]
写入局部变量表第三个位置的值 (this,0,0,exception)
局部变量表第二个位置值加一 (this,1,0,exception)
加载局部变量表第二个位置的值到操作数栈, [1]
写入局部变量表第二个位置 (this,1,0,exception)
加载局部变量表第三个位置的值到操作数栈 [0]
返回操作数栈内的值 0
翻译成伪代码是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 int i = 0 ;try { int temp = i; i = ++i; return temp; } catch (Exception e) { int temp = i; i = ++i; return temp; }
有return
和finally{}
的情况下, return
返回的值会有一个tmp
保存, 等finally{}
执行完成后, 会返回这个tmp
.
发现LocalVariableTable
里面显示的不全, locals=5
但是LocalVariableTable
里面只有3条, this
和tmp
没有显示出来.(也有可能是我理解的有问题)
finally中抛出异常 1 2 3 4 5 6 7 8 9 10 11 public class Main { public static void main (String[] args) { try { throw new RuntimeException ("try" ); } catch (Exception e) { throw new RuntimeException ("catch" ); } finally { throw new RuntimeException ("finally" ); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 0: new #2 // class java/lang/RuntimeException 3: dup 4: ldc #3 // String try 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V 9: athrow 10: astore_1 11: new #2 // class java/lang/RuntimeException 14: dup 15: ldc #6 // String catch 17: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V 20: athrow 21: astore_2 22: new #2 // class java/lang/RuntimeException 25: dup 26: ldc #7 // String finally 28: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V 31: athrow
无论如何都会抛出finally
中抛出的异常, 导致某些异常无法捕捉到, 可以使用addSuppressed(e)
, 把未被打出的异常信息记录起来, 写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Main { public static void main (String[] args) { Exception tmpException = null ; try { throw new RuntimeException ("try" ); } catch (Exception e) { tmpException = e; throw new RuntimeException ("catch" , e); } finally { try { throw new RuntimeException ("finally" ); } catch (Exception e) { tmpException.addSuppressed(e); } } } }
异常打印结果:
1 2 3 4 5 6 Exception in thread "main" java.lang.RuntimeException: catch at com.yuda.test.Main.main(Main.java:15) Caused by: java.lang.RuntimeException: try at com.yuda.test.Main.main(Main.java:12) Suppressed: java.lang.RuntimeException: finally at com.yuda.test.Main.main(Main.java:18)
线程同步 方法上的synchronized
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Main { public static void main (String[] args) { test(); } private static synchronized void test () { System.out.println(1 ); } } private static synchronized void test () ; descriptor: ()V flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNCHRONIZED Code: stack=2 , locals=0 , args_size=0 0 : getstatic #3 3 : iconst_1 4 : invokevirtual #4 7 : return LineNumberTable: line 14 : 0 line 15 : 7
用flags
来标记方法为ACC_SYNCHRONIZED
, 进入方法时, JVM会尝试获取锁, 如果是实例方法, 获取对象锁, 如果是类方法, 则获取类锁.
synchronized{}
代码块1 2 3 4 5 6 7 8 9 public class Main { public static void main (String[] args) { } private void test () { synchronized (Main.class) { System.out.println(1 ); } } }
生成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 private void test(); descriptor: ()V flags: ACC_PRIVATE Code: stack=2, locals=3, args_size=1 0: ldc #2 // class com/yuda/test/Main 2: dup 3: astore_1 4: monitorenter 5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 8: iconst_1 9: invokevirtual #4 // Method java/io/PrintStream.println:(I)V 12: aload_1 13: monitorexit 14: goto 22 17: astore_2 18: aload_1 19: monitorexit 20: aload_2 21: athrow 22: return Exception table: from to target type 5 14 17 any 17 20 17 any
将com/yuda/test/Main
压入栈
复制一个栈顶元素
放到局部变量表的2位置
monitorenter
指令, 用栈顶元素做锁.
块内打印逻辑执行
从局部变量表的2位置取出, 压入栈
monitorexit
指令, 用栈顶元素释放锁
跳转到return
.
为了保证锁一定能释放, JVM自动对synchronized{}
加了异常处理的逻辑(即finally{}
)
牛逼的文章: Synchronized解析——如果你愿意一层一层剥开我的心