#揭开JVM所看到的try/catch/finally 最近有一位朋友发了一段代码给我,这个方法很简单,具体内容大体以下:java
int num = 5000000;//500万 long begin = System.currentTimeMillis(); for(int i=0; i<num; i++){ try{ //do something }catch(Exception e){ } } long end = System.currentTimeMillis(); System.out.println("==============使用时间:" + (end - begin) + " 毫秒");
上面代码能够看到是经过执行该循环体所消耗的时间,经过和把try/cache
注释掉进行对比,最后获得的结果时间比较随机,执行的耗时和try/cache
没有必然的联系,那try/cache
究竟会不会影响代码的执行效率呢?从java语言的源码上看貌似多执行了一些指令,其实是怎么样的呢?下面我分几个场景来分析一下jvm对try/cache
的处理过程。 ##单层的try/catch 下面是一个只有单层的try/catch
代码块数组
public int test(int a,int b){ try{ return a+b; }catch (Exception e){ throw new CustomException(); } }
经过javap -v查看JVM编译成class字节码以后是如何处理这个try/catch
的jvm
public int test(int, int); flags: ACC_PUBLIC Code: stack=2, locals=4, args_size=3 0: iload_1 // 将第一个int参数压入队列(第一个入参) 1: iload_2 // 将第二个int参数压入队列(第二个入参) 2: iadd //弹出队列中第一个和第二个参数执行相加,并把相加结果压入队列 3: ireturn //弹出队列第一个元素,并return。 4: astore_3 //此处是try开始的逻辑 5: new #3 // class com/bieber/demo/CustomException 8: dup 9: invokespecial #4 // Method com/bieber/demo/CustomException."<init>":()V 12: athrow //将队列中的第一个元素弹出,并当作异常抛出,到此整个方法体完毕 Exception table: from to target type 0 3 4 Class java/lang/Exception LineNumberTable: line 13: 0 line 14: 4 line 15: 5 LocalVariableTable: Start Length Slot Name Signature 5 8 3 e Ljava/lang/Exception; 0 13 0 this Lcom/cainiao/cilogisticservice/ExceptionClass; 0 13 1 a I 0 13 2 b I StackMapTable: number_of_entries = 1 frame_type = 68 /* same_locals_1_stack_item */ stack = [ class java/lang/Exception ]
上面是test
方法JVM编译以后的结果,上面的Code
块是整个方法体的内容,而从0-3能够视为是方法体的正常逻辑,4-12能够视为try/catch块,从方法体的指令看,正常状况下执行到3
的地方就完毕了,而不会去执行4-12的指令。那是否是就得出结论,try/catch
代码块在正常逻辑的时候是不会被执行的,因而对于对代码加上try/catch
块,并不会影响代码的执行效率,由于根本不会有多余的指令被执行,只有出现异常的时候才会多出执行异常的指令。其实本文到这里基本上能够结束了,由于获得了我想要的答案(try/catch
代码块对代码性能的影响)。为了让整个问题可以更加全面一点,下面对JVM如何处理一个try/catch
作更加深刻的调研。性能
上面的JVM编译的字节码的时候除了Code
代码块,还有Exception table
代码块,从这个代码块的内容能够看到,包含四列(from,to,target,type),其中from
和to
表示这个try/catch
代码块是从哪开始到哪结束,能够看到上面的try/catch
代码块是从Code
代码块的0-3,也就是从加载第一个int值到返回结果的代码块,target
表示这个try/catch
代码块执行逻辑在哪里开始,好比上面的表示从Code
中的4开始,也就是astore_3
指令开始,直到athrow
指令被执行的地方,在Exception table
中的一行还有type
列,表示是这个异常类型,用于在一个try/catch
代码块出现多个catch
内容,用于匹配正确的异常类型。下面我列出这种状况:this
##一个try对应多个catch 我将上面的代码调整成了下面结构:code
public int test(int a,int b){ try{ return a+b; }catch (Exception e){ a++; throw new CustomException(); }catch (Throwable t){ b++; throw new CustomException(); } }
JVM对上面代码编译后的结果:xml
public int test(int, int); flags: ACC_PUBLIC Code: stack=2, locals=4, args_size=3 0: iload_1 1: iload_2 2: iadd 3: ireturn 4: astore_3 5: iinc 1, 1 8: new #3 // class com/bieber/demo/CustomException 11: dup 12: invokespecial #4 // Method com/bieber/demo/CustomException."<init>":()V 15: athrow 16: astore_3 17: iinc 2, 1 20: new #3 // class com/cainiao/cilogisticservice/CustomException 23: dup 24: invokespecial #4 // Method com/cainiao/cilogisticservice/CustomException."<init>":()V 27: athrow Exception table: from to target type 0 3 4 Class java/lang/Exception 0 3 16 Class java/lang/Throwable LineNumberTable: line 13: 0 line 14: 4 line 15: 5 line 16: 8 line 17: 16 line 18: 17 line 19: 20 LocalVariableTable: Start Length Slot Name Signature 5 11 3 e Ljava/lang/Exception; 17 11 3 t Ljava/lang/Throwable; 0 28 0 this Lcom/cainiao/cilogisticservice/ExceptionClass; 0 28 1 a I 0 28 2 b I StackMapTable: number_of_entries = 2 frame_type = 68 /* same_locals_1_stack_item */ stack = [ class java/lang/Exception ] frame_type = 75 /* same_locals_1_stack_item */ stack = [ class java/lang/Throwable ]
和上面的内容对比一下会发现,在Code
中多出了一段astore_3/athrow
块,而且在Exception table
中多了一行,想一想经过上面的解释,对这个多出的一行的目的应该都知道是用来什么的,因为我在catch
中成了throw
以外,还多了一个++
的操做,能够看到在astore_3/athrow
块中多出了iinc
指令,因此能够理解,try/catch
在JVM中对应的是一个子代码块,在条件知足(出现匹配的catch异常)的时候会被执行。队列
下面我整理一下当出现异常的(这里说的是有try/catch
的异常)JVM处理流程:ci
一、在try/catch出现异常 二、JVM会去`Exception table`查找匹配的异常类型 三、假设匹配上了,那么读取from,to,target,获取待执行的`try/catch`块的指令(具体是否抛出,看是否有athrow指令)。
为了更加了解JVM对try
的处理,下面对try/finally
再调研一下。get
##try/finally块的执行处理
调整代码逻辑,以下:
public int test(int a,int b){ try{ return a+b; }catch (Exception e){ a++; throw new CustomException(); }finally { b++; } }
JVM编译后的指令:
public int test(int, int); flags: ACC_PUBLIC Code: stack=2, locals=5, args_size=3 0: iload_1 1: iload_2 2: iadd 3: istore_3 //将栈顶的元素存储局部变量数组的第三个位置 4: iinc 2, 1 //执行b++ 7: iload_3 //把局部变量第三个位置的数值压入栈顶 8: ireturn //弹出栈顶,而且返回 9: astore_3 10: iinc 1, 1 //a++ 13: new #3 // class com/bieber/demo/CustomException 16: dup 17: invokespecial #4 // Method com/bieber/demo/CustomException."<init>":()V 20: athrow 21: astore 4 23: iinc 2, 1 //b++ 26: aload 4 28: athrow Exception table: from to target type 0 4 9 Class java/lang/Exception 0 4 21 any 9 23 21 any LineNumberTable: line 13: 0 line 18: 4 line 14: 9 line 15: 10 line 16: 13 line 18: 21 LocalVariableTable: Start Length Slot Name Signature 10 11 3 e Ljava/lang/Exception; 0 29 0 this Lcom/cainiao/cilogisticservice/ExceptionClass; 0 29 1 a I 0 29 2 b I StackMapTable: number_of_entries = 2 frame_type = 73 /* same_locals_1_stack_item */ stack = [ class java/lang/Exception ] frame_type = 75 /* same_locals_1_stack_item */ stack = [ class java/lang/Throwable ]
经过上面的代码,你会发如今Exception table
都出了两行,其实咱们只是在代码中只有一个try/catch
块,而这里出现了三个,那么另外两个是作什么的呢?能够看到多出的两行的type都是any
,这里的any
表示的是任何异常类型,多出的第一行,是从0-4
,表示0-4
之间的指令出现异常,会从21
的指令开始执行,发现执行的是b++
(finally)的内容,多出的第二行是9-23
,表示9-23
之间的指令被执行的过程当中出现异常也会从21
行开始执行(也是执行finally
的内容),而9-23
实际上是catch
的代码逻辑。上面均是出现了异常会触发finally
的代码执行,正常状况下会发现4
的位置执行了finally
的内容,而后再执行ireturn
指令,这里能够得出,JVM处理finally
实际上是对于正常的指令队列增长了finally
代码块的指令,以及对异常中添加了finally
代码块的指令,这也就致使了fianlly
在任何地方均可以被执行,其实就是冗余了指令队列(其实思想比较简单)。
到此,对JVM如何处理try/catch/finally
块进行了简单的介绍,目的是让你们对添加try
代码块不要吝啬,在须要的时候,仍是须要作一些异常的控制,让代码的异常逻辑更加完善,而不是一直将异常抛给外面处理,由于外面可能并不知道你这个异常是什么意思。