Asm 之 TryCatchBlockSorter 源码分析

JVM 异常处理机制

代码示例

以下的代码中有两个 Try catch 处理异常,其中第二个 try 有 finaly语句。

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
package com.example.trycatch;
/**
* 测试Try catch的嵌套效果
* @author gang
*
*/
public class TryNested {

public void test(){

try {

try {
int a = 1/0;
} catch (RuntimeException e) {
System.out.println("Second");
} finally{
System.out.println("finally");
}


} catch (Exception e) {
System.out.println("First");
}

}
}

字节码分析

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
  public test()V
TRYCATCHBLOCK L0 L1 L2 java/lang/RuntimeException
TRYCATCHBLOCK L0 L3 L4
TRYCATCHBLOCK L0 L5 L6 java/lang/Exception
L0
LINENUMBER 14 L0
ICONST_1
ICONST_0
IDIV
ISTORE 1
L1
LINENUMBER 15 L1
GOTO L7
L2
FRAME SAME1 java/lang/RuntimeException
ASTORE 1
L8
LINENUMBER 16 L8
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "Second"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L3
LINENUMBER 18 L3
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "finally"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
GOTO L9
L4
LINENUMBER 17 L4
FRAME SAME1 java/lang/Throwable
ASTORE 2
L10
LINENUMBER 18 L10
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "finally"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L11
LINENUMBER 19 L11
ALOAD 2
ATHROW
L7
LINENUMBER 18 L7
FRAME SAME
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "finally"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L5
LINENUMBER 22 L5
GOTO L9
L6
FRAME SAME1 java/lang/Exception
ASTORE 1
L12
LINENUMBER 23 L12
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "First"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L9
LINENUMBER 26 L9
FRAME SAME
RETURN
L13
LOCALVARIABLE this Lcom/example/trycatch/TryNested; L0 L13 0
LOCALVARIABLE e Ljava/lang/RuntimeException; L8 L3 1
LOCALVARIABLE e Ljava/lang/Exception; L12 L9 1
MAXSTACK = 2
MAXLOCALS = 3
}

以上字节码中有三个 TRYCATCHBLOCK ,其为 Class 的 Exception Table。

1
2
3
TRYCATCHBLOCK L0 L1 L2 java/lang/RuntimeException
TRYCATCHBLOCK L0 L3 L4
TRYCATCHBLOCK L0 L5 L6 java/lang/Exception

一个TRYCATCHBLOCK 有四部分内容组成:

  • start lable: Try 开始的位置
  • end lable: Try 结束的位置
  • handler lable: 如果捕获异常,跳转的位置。
  • internal name:捕获异常的internal name。

start lable 和 end lable 决定了 try 语句的范围,在如果在此范围内捕获到异常,JVM 会跳转到 handler 的位置执行指令。

如何处理处理finally

TRYCATCHBLOCK 中并没有单独处理 finally 内容,finally 也是使用的TRYCATCHBLOCK ,但是 其internal name 为 空,如果不指定异常的 internal name ,表示发生任何异常都会跳转到handler的位置执行,在以上的Exception Table 中第二个TRYCATCHBLOCK 就是用来处理 finally 语句的。

Exception Table 顺序

程序中 Try 语句是存在嵌套情况的,JVM 在运行指令的时候,如果发生异常,就会在 Exception Table 中按照顺序查找匹配的TRYCATCHBLOCK,如果找到匹配的TRYCATCHBLOCK 就会跳转到其匹配的handler的位置。在嵌套的Try语句中,内部的 Try 语句对应的 TRYCATCHBLOCK 应该排在 Exception Table 前面的位置, 否则跳转的位置就会出错,这也是为什么需要 TryCatchBlockSorter 来进行 Exception Table 排序。

TryCatchBlockSorter 源码分析

异常列表排序算法

在以上内容中提到,嵌套的 Try 语句中内部的 Try 语句 应该在 Exception Table 前面, 如何确定 Try 语句是在内部还是再外部?,TryCatchBlockSorter 通过一个简单算法来实现,就是通过 start lable 和 end lable的范围来确定,范围大的排在Exception Table 后面。
简单来说就是相当于 try语句结束的代码行减去try 语句结束的代码行,结果大的排在后面。(ASM 实际上通过 start lable 和 end lable 指令的位置来计算的)。
如果两个 Try 语句 不存在 嵌套,谁排在前面或者后面是没有影响的。

TryCatchBlockSorter 代码

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
45
46
47
48
49
50
51
52
53
54
55
56
/**
* TryCatchBlockSorter 继承了MethodNode ,可以直接 排序 tryCatchBlocks
*/
public class TryCatchBlockSorter extends MethodNode {

public TryCatchBlockSorter(final MethodVisitor mv, final int access,
final String name, final String desc, final String signature,
final String[] exceptions) {
this(Opcodes.ASM5, mv, access, name, desc, signature, exceptions);
}

protected TryCatchBlockSorter(final int api, final MethodVisitor mv,
final int access, final String name, final String desc,
final String signature, final String[] exceptions) {
super(api, access, name, desc, signature, exceptions);
this.mv = mv;
}

@Override
public void visitEnd() {
// Compares TryCatchBlockNodes by the length of their "try" block.
Comparator<TryCatchBlockNode> comp = new Comparator<TryCatchBlockNode>() {

/**
* 范围小的排在 Exception table的 前面
*/
public int compare(TryCatchBlockNode t1, TryCatchBlockNode t2) {
int len1 = blockLength(t1);
int len2 = blockLength(t2);
return len1 - len2;
}
/**
* 通过 start end lable 计算 try 语句的范围。
*/
private int blockLength(TryCatchBlockNode block) {
int startidx = instructions.indexOf(block.start);
int endidx = instructions.indexOf(block.end);
return endidx - startidx;
}
};
/**
* 进行排序
*/
Collections.sort(tryCatchBlocks, comp);
// Updates the 'target' of each try catch block annotation.
/**
* 更新关联的 Type Annotation
*/
for (int i = 0; i < tryCatchBlocks.size(); ++i) {
tryCatchBlocks.get(i).updateIndex(i);
}
if (mv != null) {
accept(mv);
}
}
}