0 前言

关于java字节码指令的含义,可以参考这篇博客:Java字节码指令含义解释与指令查询

1 try..catch..finally

1.1 catch

1.1.1 catch单个异常

查看相应的java代码:

1
2
3
4
5
6
7
8
9
10
public class FinallyDemo {
public static void main(String[] args) {
int i = 10;
try{
i = 20;
}catch (NegativeArraySizeException e){
i = 30;
}
}
}

接下来通过javap -v xxx.class命令,来查看该段源码的字节码反编译之后的信息:

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
{ //源代码对应的字节码指令
public com.basic.FinallyDemo();
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 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/basic/FinallyDemo;

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: bipush 10 //将10压入栈顶
2: istore_1 //将10存储到slot 1中,即10 -> i
3: bipush 20 //将20压入栈顶, try-----开始
5: istore_1 //将20存储到slot 1中,即20 -> i try------结束
6: goto 13 //这里没有判断条件,但是有一个goto指令,结合异常表一起执行
9: astore_2 //存储异常信息到slot 2,即 异常信息 -> e
10: bipush 30 //将30压入栈顶,
12: istore_1 //将30存储到slot 1中,即 30 -> i
13: return //返回栈顶元素
Exception table: //被try..catch包裹的代码,会产生一个异常表
from to target type
3 6 9 Class java/lang/NegativeArraySizeException //监测try中的代码,如果发生异常,跳转到第9行
LineNumberTable:
line 5: 0
line 7: 3
line 10: 6
line 8: 9
line 9: 10
line 11: 13
LocalVariableTable:
Start Length Slot Name Signature
10 3 2 e Ljava/lang/NegativeArraySizeException;
0 14 0 args [Ljava/lang/String;
3 11 1 i I
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 9
locals = [ class "[Ljava/lang/String;", int ]
stack = [ class java/lang/NegativeArraySizeException ]
frame_type = 3 /* same */
}
SourceFile: "FinallyDemo.java"

从反编译之后的字节码信息可以看到,之所以能执行catch中的代码,是因为有一个异常表(exception table)来存放trycatch的执行的起始位置,当检测到try中有异常发生时,跳转到catch中执行。

1.1.2 catch多个异常

先看java源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.nio.file.FileSystemAlreadyExistsException;

public class FinallyDemo {
public static void main(String[] args) {
int i = 10;
try{
i = 20;
}catch (NegativeArraySizeException e){
i = 30;
}catch (NullPointerException e){
i = 40;
}catch (FileSystemAlreadyExistsException e){
i = 50;
}
System.out.println(i);
}
}

源代码对应的字节码指令:

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
69
70
71
72
73
74
75
76
77
{
public com.basic.FinallyDemo();
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 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/basic/FinallyDemo;

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: bipush 10 //定义变量i,将10压入栈顶
2: istore_1
3: bipush 20 //try------开始
5: istore_1 //try------结束
6: goto 27
9: astore_2 //catch1-----开始
10: bipush 30
12: istore_1 //catch2----结束
13: goto 27
16: astore_2 //catch2-----开始
17: bipush 40
19: istore_1 //catch2-----结束
20: goto 27
23: astore_2 //catch3-----开始
24: bipush 50
26: istore_1 //catch3------结束
27: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
30: iload_1
31: invokevirtual #6 // Method java/io/PrintStream.println:(I)V
34: return
Exception table: //异常表中有三个异常,都是监测try中的代码
from to target type
3 6 9 Class java/lang/NegativeArraySizeException
3 6 16 Class java/lang/NullPointerException
3 6 23 Class java/nio/file/FileSystemAlreadyExistsException
LineNumberTable:
line 8: 0
line 10: 3
line 17: 6
line 11: 9
line 12: 10
line 17: 13
line 13: 16
line 14: 17
line 17: 20
line 15: 23
line 16: 24
line 18: 27
line 19: 34
LocalVariableTable:
Start Length Slot Name Signature
10 3 2 e Ljava/lang/NegativeArraySizeException;
17 3 2 e Ljava/lang/NullPointerException;
24 3 2 e Ljava/nio/file/FileSystemAlreadyExistsException;
0 35 0 args [Ljava/lang/String;
3 32 1 i I
StackMapTable: number_of_entries = 4
frame_type = 255 /* full_frame */
offset_delta = 9
locals = [ class "[Ljava/lang/String;", int ]
stack = [ class java/lang/NegativeArraySizeException ]
frame_type = 70 /* same_locals_1_stack_item */
stack = [ class java/lang/NullPointerException ]
frame_type = 70 /* same_locals_1_stack_item */
stack = [ class java/nio/file/FileSystemAlreadyExistsException ]
frame_type = 3 /* same */
}

1.1.3 catch多个异常的简写

这是上一小节中的catch多个异常的简写形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.nio.file.FileSystemAlreadyExistsException;

public class FinallyDemo {
public static void main(String[] args) {
int i = 10;
try{
i = 20;
}catch (NegativeArraySizeException | NullPointerException | FileSystemAlreadyExistsException e){
i = 30;
}
System.out.println(i);
}
}

对应的字节码信息和上一节中的相同:

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
{
public com.basic.FinallyDemo();
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 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/basic/FinallyDemo;

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: bipush 10
2: istore_1
3: bipush 20
5: istore_1
6: goto 13
9: astore_2
10: bipush 30
12: istore_1
13: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
16: iload_1
17: invokevirtual #6 // Method java/io/PrintStream.println:(I)V
20: return
Exception table:
from to target type
3 6 9 Class java/lang/NegativeArraySizeException
3 6 9 Class java/lang/NullPointerException
3 6 9 Class java/nio/file/FileSystemAlreadyExistsException
LineNumberTable:
line 8: 0
line 10: 3
line 13: 6
line 11: 9
line 12: 10
line 14: 13
line 15: 20
LocalVariableTable:
Start Length Slot Name Signature
10 3 2 e Ljava/lang/RuntimeException;
0 21 0 args [Ljava/lang/String;
3 18 1 i I
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 9
locals = [ class "[Ljava/lang/String;", int ]
stack = [ class java/lang/RuntimeException ]
frame_type = 3 /* same */
}

1.2 finally

1.2.1 在finally中return *;

查看FinallyDemo.java文件的代码,试问最终输出多少?10? or 20? or 30?

FinallyDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class FinallyDemo {
public static void main(String[] args) {
FinallyDemo finallyDemo = new FinallyDemo();
int t = finallyDemo.test();
System.out.println(t);
}

public int test(){
int i = 10;
try {
return 20;
}finally {
return 30;
}
}
}

运行结果:

30

通过对其FinallyDemo.class文件进行反编译,可以分析得到为什么最终结果会是30。为了简化代码,这里就直接将源代码对应的字节码信息粘贴过来。

FinallyDemo.class

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
69
70
71
72
73
74
{
public com.basic.FinallyDemo();
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 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/basic/FinallyDemo;

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: new #2 // class com/basic/FinallyDemo 在堆中创建对象
3: dup
4: invokespecial #3 // Method "<init>":()V 调用FinallyDemo构造方法
7: astore_1 // 将新创建的对象存储到局部变量slot 1中
8: aload_1 //加载局部变量slot 1中的对象
9: invokevirtual #4 // Method test:()I //执行slot 1中的test()方法
12: istore_2 //将test()返回的int类型存储到int类型的局部变量slot 2中
13: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 获得静态输出方法
16: iload_2 //将在需要打印的变量 slot 2
17: invokevirtual #6 // Method java/io/PrintStream.println:(I)V 调用静态对象的println方法,并调用需要打印的变量
20: return
LineNumberTable:
line 5: 0
line 6: 8
line 7: 13
line 8: 20
LocalVariableTable:
Start Length Slot Name Signature
0 21 0 args [Ljava/lang/String;
8 13 1 finallyDemo Lcom/basic/FinallyDemo;
13 8 2 t I

public int test();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=4, args_size=1
0: bipush 10 //将10压入栈顶
2: istore_1 // 10 -> i
3: bipush 20 //在try中------, 将20压入栈顶
5: istore_2 //20 -> slot 2 该局部变量是一个隐藏的局部变量,LocalVariableTable不显示
6: bipush 30 //将30压入栈顶
8: ireturn //----try结束 返回栈顶元素 30
9: astore_3 //在finally中-------
10: bipush 30 //将30压入栈顶
12: ireturn //finally------结束,返回栈顶元素
Exception table:
from to target type
3 6 9 any
LineNumberTable:
line 11: 0
line 13: 3
line 15: 6
LocalVariableTable:
Start Length Slot Name Signature
0 13 0 this Lcom/basic/FinallyDemo;
3 10 1 i I
StackMapTable: number_of_entries = 1
frame_type = 255 /* full_frame */
offset_delta = 9
locals = [ class com/basic/FinallyDemo, int ]
stack = [ class java/lang/Throwable ]
}
SourceFile: "FinallyDemo.java"
  • 分析:

    我们从反编译之后的字节码可以看出,之所以finally中的代码最后都会被执行,是因为在字节码中,将finally中的代码复制了一份,所以才会总是执行。

1.2.2 在finally中使用return的弊端

使用try..catch捕捉程序异常,但是有些时候,catch不能将所有的异常都捕捉到,此时那些没有被catch捕捉到的异常就会在finally中被再次捕捉到,这样就提高了程序运行的安全性。但是当在finally中使用return语句后,那么finally就会将其他的异常”吞掉“,也就是不再捕捉catch中没有被捕捉的异常。

1.2.2.1 finally中没有加return语句:
1
2
3
4
5
6
7
8
9
10
11
12
public class FinallyDemo {
public static void main(String[] args) {
int i = 10;
try{
i = 20;
}catch (NegativeArraySizeException e){
e.printStackTrace();
}finally {
i = 30;
}
}
}

为了简便,这里主要粘贴源代码对应的字节码信息:

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
69
70
{
public com.basic.FinallyDemo();
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 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/basic/FinallyDemo;

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=4, args_size=1
0: bipush 10 //将10压入栈顶,全部变量
2: istore_1 //将10存储到slot 1中,10 -> i
3: bipush 20 //try-----开始, 将20压入栈顶
5: istore_1 //将20存储到slot 1中, 20 -> i
6: bipush 30 //将30压入栈顶,
8: istore_1 //将30存储到slot 1中,30 -> i try----结束
9: goto 29
12: astore_2 //catch------开始
13: aload_2
14: invokevirtual #3 // Method java/lang/NegativeArraySizeException.printStackTrace:()V
17: bipush 30
19: istore_1 //catch------结束
20: goto 29
23: astore_3 //finally----开始
24: bipush 30
26: istore_1
27: aload_3
28: athrow //finally------结束,抛出在finally里中捕捉到的异常
29: return
Exception table: //异常表,存放异常的跳转的起始位置,左闭右开区间,以第一个为例,从第3行到第5行
from to target type
3 6 12 Class java/lang/NegativeArraySizeException //监测try中的代码
3 6 23 any //finally在try中的跳转
12 17 23 any //finally在catch中的跳转
LineNumberTable:
line 5: 0
line 7: 3
line 11: 6
line 12: 9
line 8: 12
line 9: 13
line 11: 17
line 12: 20
line 11: 23
line 12: 27
line 13: 29
LocalVariableTable:
Start Length Slot Name Signature
13 4 2 e Ljava/lang/NegativeArraySizeException;
0 30 0 args [Ljava/lang/String;
3 27 1 i I
StackMapTable: number_of_entries = 3
frame_type = 255 /* full_frame */
offset_delta = 12
locals = [ class "[Ljava/lang/String;", int ]
stack = [ class java/lang/NegativeArraySizeException ]
frame_type = 74 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
frame_type = 5 /* same */
}
1.2.2.2 finally中加了return语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class FinallyDemo {
public static void main(String[] args) {
int j = test();
System.out.println(j);
}

public static int test(){
int i = 0;
try{
return 10;
}finally {
return 20;
}
}
}

源代码对应的字节码:

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
{
public com.basic.FinallyDemo();
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 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/basic/FinallyDemo;

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: invokestatic #2 // Method test:()I
3: istore_1
4: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
7: iload_1
8: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
11: return
LineNumberTable:
line 8: 0
line 9: 4
line 10: 11
LocalVariableTable:
Start Length Slot Name Signature
0 12 0 args [Ljava/lang/String;
4 8 1 j I

public static int test();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=0
0: iconst_0 //定义变量i
1: istore_0
2: bipush 10 //try-----开始
4: istore_1
5: bipush 20
7: ireturn //try-----结束
8: astore_2 //finally---开始
9: bipush 20 // finally中并没有捕捉异常,也没有抛出异常
11: ireturn //finally---结束
Exception table:
from to target type
2 5 8 any
LineNumberTable:
line 13: 0
line 15: 2
line 17: 5
LocalVariableTable:
Start Length Slot Name Signature
2 10 0 i I
StackMapTable: number_of_entries = 1
frame_type = 255 /* full_frame */
offset_delta = 8
locals = [ int ]
stack = [ class java/lang/Throwable ]
}

我们从字节码中可以看到,finally中并没有捕捉异常,也没有抛出异常,所以此种写法对于程序而言非常危险。下面看一段错误代码,但是程序并没有抛出异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class FinallyDemo {
public static void main(String[] args) {
int j = test();
System.out.println(j);
}

public static int test(){
int i = 1;
try{
i = i / 0; //此处是一个异常代码
return i;
}finally {
return i;
}
}
}

运行结果:

程序没有抛出异常

所以,在写代码时,一定要主要不要写出这样的代码。

写在最后

欢迎大家关注鄙人的公众号【麦田里的守望者zhg】,让我们一起成长,谢谢。
微信公众号