1、Java异常处理机制
package com.bytecode;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
public class MyTest3 {
public void test(){
try{
InputStream is = new FileInputStream("test.txt");
ServerSocket serverSocket = new ServerSocket(9999);
serverSocket.accept();
} catch (FileNotFoundException ex){
} catch (IOException ex){
} catch (Exception ex){
} finally {
System.out.println("finally");
}
}
}
反编译后的结构(test方法部分)
public void test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=4, args_size=1
0: new #2 // class java/io/FileInputStream
3: dup
4: ldc #3 // String test.txt
6: invokespecial #4 // Method java/io/FileInputStream."<init>":(Ljava/lang/String;)V
9: astore_1
10: new #5 // class java/net/ServerSocket
13: dup
14: sipush 9999
17: invokespecial #6 // Method java/net/ServerSocket."<init>":(I)V
20: astore_2
21: aload_2
22: invokevirtual #7 // Method java/net/ServerSocket.accept:()Ljava/net/Socket;
25: pop
26: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
29: ldc #9 // String finally
31: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
34: goto 84
37: astore_1
38: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
41: ldc #9 // String finally
43: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
46: goto 84
49: astore_1
50: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
53: ldc #9 // String finally
55: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
58: goto 84
61: astore_1
62: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
65: ldc #9 // String finally
67: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
70: goto 84
73: astore_3
74: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
77: ldc #9 // String finally
79: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
82: aload_3
83: athrow
84: return
Exception table:
from to target type
0 26 37 Class java/io/FileNotFoundException
0 26 49 Class java/io/IOException
0 26 61 Class java/lang/Exception
0 26 73 any
LineNumberTable:
line 13: 0
line 15: 10
line 16: 21
line 24: 26
line 25: 34
line 17: 37
line 24: 38
line 25: 46
line 19: 49
line 24: 50
line 25: 58
line 21: 61
line 24: 62
line 25: 70
line 24: 73
line 25: 82
line 26: 84
LocalVariableTable:
Start Length Slot Name Signature
10 16 1 is Ljava/io/InputStream;
21 5 2 serverSocket Ljava/net/ServerSocket;
0 85 0 this Lcom/bytecode/MyTest3;
StackMapTable: number_of_entries = 5
frame_type = 101 /* same_locals_1_stack_item */
stack = [ class java/io/FileNotFoundException ]
frame_type = 75 /* same_locals_1_stack_item */
stack = [ class java/io/IOException ]
frame_type = 75 /* same_locals_1_stack_item */
stack = [ class java/lang/Exception ]
frame_type = 75 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
frame_type = 10 /* same */
问题一:为什么locals=4,args_size=1?
args_size表示传进来的参数,是this,local表示最多有4个局部变量,分别是this,is,serverSocket,和有可能抛出异常的某个ex(可能是FileNotFoundException,IOException,Exception其中一个,抛出异常时,会把异常赋值到相应的ex中)
异常表的结构
从字节码1内容可以知道,code的结构
Code_attribute{
u2 attribute_name_index;
u4 attribute_length;
u2 max_stack;
u2 max_locals;
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{
u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
每个exception_table表项由start_pc, end_pc, handler_pc, catch_type组成
- ①
start_pc
和end_pc
表示在code
数组中的从start_pc
到end_pc
处(包含start_pc
,不包含end_pc
)的指令抛出的异常会由这个表项来处理 - ②
handler_pc
表示处理异常代码的开始处。catch_type
表示会被处理异常类型,它指向常量池里的一个异常类。当catch_type
为0
时,表示处理所有的异常
上面的代码中的异常表
Exception table:
from to target type
0 26 37 Class java/io/FileNotFoundException
0 26 49 Class java/io/IOException
0 26 61 Class java/lang/Exception
0 26 73 any
例如第一个,在代码从0到25行中,若存在的异常类型是Class java/io/FileNotFoundException,则跳转到第27行,最后每个异常后面都有一个goto助记符跳到return,type的any类型只的是除了上面3种异常以外的异常
Java字节码对于异常的处理方式:
- 1、统一采用异常表的方式对异常进行处理
- 2、在之前的JDK 1.4.2之前的版本中并不是使用异常表的方式来对异常进行处理的,而是采用特定的指令方式。【了解既可】
- 3、当异常处理存在
finally
语句时,现代化的jvm
采取的处理方式是将finally
语句块的字节码拼接到每一个catch
块后面。换句话说,程序中存在多少个catch
块,就会在每一个catch
块后面重复多少个finally
语句块的字节码
image.png
2、栈帧与操作数栈剖析
栈
虚拟机栈:表示Java方法执行的内存模型,每调用一个方法就会为每个方法生成一个栈帧(Stack Frame),用来存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法被调用和完成的过程,都对应一个栈帧从虚拟机栈上入栈和出栈的过程。虚拟机栈的生命周期和线程是相同的。
不过我们这里要说的栈还不是虚拟机栈,而是虚拟机栈里,一个栈帧的操作数栈。因为,我这里只是演示一个方法而已,这一个方法其实就是一个栈帧。

一个栈帧主要由四部分组成:
- 局部变量表
- 操作栈(也叫操作数栈)
- 动态连接
- 返回地址信息
通过字节码图解代码的详细步骤例子见这里
https://www.jianshu.com/p/614635ac3cca
另外再来说一下局部变量,存储局部变量的最小单位称为slot,比如说有10个局部变量则会有10个slot来进行存储,但是这也不是绝对的,因为数据类型的长度是不一样的,像比较短的short、int等类型的长度较短,一个slot就可以存储下,那对于long、double占据长度比较多的类型就会用两个连续的slot来表示,当然啦对于要多个slot来表示的一个局部变量要读的话也是读连续的几个slot,不然就数据不对了;另外对于10个局部变量不是一定得要10个slot来存储的,因为slot是可以复用的,我们知道方法体中的局部变量出了方法作用域就会回收,但是方法体里面又是可以N多个更小的作用域的,怎么理解,写一个伪代码:

而对于方法局部变量表中是不会区分是否是更小的作用域的,都当一个局部变量对待,而如果像上面b和c出了作用域生命周期结束之后,那么这两个局部变量的slot有可能会给下面的d和e重复使用,当然啦也有可能不复用,具体得看JVM的具体实现,总之需要明确的是:对于10个局部变量,有可能需要10个slot,也有可能不需要这么多。
3、方法重载与invokevirtual字节码指令的关系
静态解析:有些符号引用是在类加载阶段或是第一次使用时就会转换成直接引用,这种转换叫做静态解析;
动态链接:另外一些符号引用则是在每次运行期转换为直接引用,这种转换叫做动态链接
方法调用的助记符:
1、invokeinteface:调用接口中的方法,实际上是在运行期决定的,决定到底调用实现该接口的哪个对象的特定方法
2、invokestatic:调用静态方法
3、invokespecial:调用自己的私有方法,构造方法(<init>)以及父类的方法
4、invokevirtual:调用虚方法,运行期动态查找的过程
5、invokedynamic:动态调用方法,这是五个调用中最为复杂的,但是它是在JDK1.7之后才引用的,本身Java是一门静态的语言,但是通过一些引擎可以调用Javascript,这里了解一下既可。
静态解析的4中情况:
1、静态方法
2、父类方法
3、构造方法
4、私有方法(无法被重写)
以上4类方法称作非虚方法,他们是在类加载阶段就可以将符号引用转化为直接引用
方法重载
package com.bytecode;
public class MyTest5 {
public void test(Grandpa grandpa) {
System.out.println("grandpa");
}
public void test(Father father) {
System.out.println("father");
}
public void test(Son son) {
System.out.println("son");
}
public static void main(String[] args) {
Grandpa g1 = new Father();
Grandpa g2 = new Son();
MyTest5 myTest5 = new MyTest5();
myTest5.test(g1);
myTest5.test(g2);
}
}
class Grandpa {
}
class Father extends Grandpa {
}
class Son extends Father {
}
输出
grandpa
grandpa
方法的静态分派。
Grandpa g1 = new Father();
以上代码,g1的静态类型是Grandpa,而g1的实际类型(真正指向的类型)是Father。
我们可以得到出这样一个结论:变量的静态类型是不会发生变化的,而变量的实际类型则是可以发生变化的(多态的一种体现),实际类型是在运行期方可确定
方法重载,是一种静态的行为,编译器就可以完全确定,既然是静态的行为,那么调用方法传的参数则会只认变量的静态类型,而非变量的实际类型
new 操作完成了3件事情
1、为对象在内存(堆上)开辟一块内存空间
2、执行构造方法
3、将构造方法执行完后返回这个对象的引用
方法重写
package com.bytecode;
public class MyTest6 {
public static void main(String[] args) {
Fruit apple = new Apple();
Fruit orange = new Orange();
apple.test();
orange.test();
apple = new Orange();
apple.test();
}
}
class Fruit {
public void test() {
System.out.println("Fruit");
}
}
class Apple extends Fruit {
@Override
public void test() {
System.out.println("Apple");
}
}
class Orange extends Fruit {
@Override
public void test() {
System.out.println("Orange");
}
}
输出
Apple
Orange
Orange
主函数
public static void main(String[] args) {
Fruit apple = new Apple();
Fruit orange = new Orange();
apple.test();
orange.test();
apple = new Orange();
apple.test();
}
反编译
0: new #2 // class com/bytecode/Apple
3: dup
4: invokespecial #3 // Method com/bytecode/Apple."<init>":()V
7: astore_1
8: new #4 // class com/bytecode/Orange
11: dup
12: invokespecial #5 // Method com/bytecode/Orange."<init>":()V
15: astore_2
16: aload_1
17: invokevirtual #6 // Method com/bytecode/Fruit.test:()V
20: aload_2
21: invokevirtual #6 // Method com/bytecode/Fruit.test:()V
24: new #4 // class com/bytecode/Orange
27: dup
28: invokespecial #5 // Method com/bytecode/Orange."<init>":()V
31: astore_1
32: aload_1
33: invokevirtual #6 // Method com/bytecode/Fruit.test:()V
36: return
0: new (给它分配一个内存空间,生成Apple的实例,并将其引用值压入栈顶)
3: dup (复制操作数栈顶层的值,并把复制后的结果压入到操作数栈中)
4: invokespecial (调用Apple的构造方法)
7: astore_1 (将栈顶引用型数值存入索引为1的局部变量当中)
8: new (给它分配一个内存空间,生成Orange的实例,并将其引用值压入栈顶)
11: dup (复制操作数栈顶层的值,并把复制后的结果压入到操作数栈中)
12: invokespecial (调用Orange的构造方法)
15: astore_2 (将栈顶引用型数值存入索引为2的局部变量当中)
16: aload_1 (将局部变量表中索引为1的局部变量推送至栈顶)
17: invokevirtual
20: aload_2 (将局部变量表中索引为2的局部变量推送至栈顶)
21: invokevirtual
24: new (给它分配一个内存空间,生成Orange的实例,并将其引用值压入栈顶)
27: dup (复制操作数栈顶层的值,并把复制后的结果压入到操作数栈中)
28: invokespecial (调用Orange的构造方法)
31: astore_1 (将栈顶引用型数值存入索引为1的局部变量当中,之前那个相当于可以回收了)
32: aload_1 (从局部变量当中加载索引为1的引用apple)
33: invokevirtual
36: return
apple的new过程

方法的动态分派
取决于方法的接受者(具体哪个真正的实例)
invokevirtual字节码指令涉及到多态查找的流程,具体流程是:
- 1、首先要操作数栈的栈顶去寻找到栈顶的元素所指向对象的实际类型。
上述例子中的代码 和 对应字节码,前面是有一个aload的操作将局部变量表中索引为1的局部变量推送至栈顶,因此栈顶元素一定是apple的实例
apple.test()
16: aload_1 (将局部变量表中索引为1的局部变量推送至栈顶)
17: invokevirtual
-
2、在该实际类型的对象当中,如果寻找到了与常量池中描述符和名称都相同的方法,并且具备相应的访问权限,就会直接返回目标方法的直接引用(就是Apple.test())
-
3、如果在实际类型的对象中没有找到该方法,那么就去其父类,继续执行该查找流程,直到找到,或者抛出异常
https://blog.csdn.net/xyh930929/article/details/84067186?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-6.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-6.control
为什么invokevirtual 可以重载,又可以重写
https://www.cnblogs.com/mengchunchen/p/7860397.html
网友评论