您的位置:首页 > 其它

揭开JVM所看到的try/catch/finally

2016-06-30 11:23 211 查看
#揭开JVM所看到的try/catch/finally
最近有一位朋友发了一段代码给我,这个方法很简单,具体内容大致如下:

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


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
内容,用于匹配正确的异常类型。下面我列出这种情况:

##一个try对应多个catch
我将上面的代码调整成了下面结构:

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对上面代码编译后的结果:

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处理流程:

1、在try/catch出现异常
2、JVM会去`Exception table`查找匹配的异常类型
3、假设匹配上了,那么读取from,to,target,获取待执行的`try/catch`块的指令(具体是否抛出,看是否有athrow指令)。

为了更加了解JVM对
try
的处理,下面对
try/finally
再调研一下。

##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
代码块不要吝啬,在需要的时候,还是需要做一些异常的控制,让代码的异常逻辑更加完善,而不是一直将异常抛给外面处理,因为外面可能并不知道你这个异常是什么意思。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  jvm try catch finally