谈谈JVM this关键字和异常表的作用
你知道的越多,不知道的就越多,业余的像一棵小草!
你来,我们一起精进!你不来,我和你的竞争对手一起精进!
编辑:业余草
juejin.cn/post/7003717101537984525
推荐:https://www.xttblog.com/?p=5357
先从段代码说起
我历史文章中已经写过关于 this 这个关键字的秘密了(《阿里面试题:Java中this和super关键字的底层实现原理》),不知道还有多少人记得。今天我们继续聊聊 this 的底层实现以及 Java 中 Exception 的底层实现。
关于异常和this的
代码
public class ExceptionTest {
public void test(){
try{
InputStream inputStream = new FileInputStream("test.cpp");
ServerSocket serverSocket = new ServerSocket(3306);
serverSocket.accept();
}catch (FileNotFoundException notFound){
}catch (IOException ioException){
}catch (Exception exception){
}finally{
System.out.println("finally");
}
}
}
反编译截图如下:
❝「在Java代码层面没有参数,但是在字节码层面是有参数的。」
❞
隐藏的实例方法参数就是 「this
」
对于Java类中的每一个实例方法(「非static方法」),其在编译后所生成的字节码当中,方法参数的数量总是会比源代码中方法参数的数量多一个(this),它位于方法的第一个参数位置处;这样,我们就可以在Java的实例方法中「使用this去访问当前对象的属性以及其他方法。」 这个操作是在编译期间完成的,即由javac编译器在编译的时候将对this的访问转化为对一个普通实例方法参数的访问,接下来在运行期间,由JVM在调用实例方法时,自动向实例方法传入该this参数。所以,在实例方法的局部变量表中,「至少会有一个指向当前对象的局部变量。」
再解释max_locals(最大局部变量)
该代码 max_locals 为什么是 4?
代码的try中有两个显式定义的局部变量,这就有2个了。 再加上隐藏的this,这一共就有3个了。 最后,留一个给Exception局部变量,虽然代码中有多个catch,但是,如果有异常的话只有一个catch会被执行。可以理解为这个Exception局部变量会在运行期间等待着被传入catch(类似作为它的参数传入)。这样一共就有4个局部变量了。
异常表
在说异常表之前,先复习一下字节码Code属性的结构
「
attribute length
」 表示attribute所包含的字节数,「不包含」attribute_name_index和attribute_length字段。「
max_stack
」 表示这个方法运行的任何时刻所能达到的操作数栈的最大深度。「
max_locals
」 表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量。「
code_length
」 表示该方法所包含的字节码的字节数以及具体的指令码。具体字节码即是该方法被调用时,虚拟机所执行的字节码。
「
exception_table
」 这里存放的是处理异常的信息。每个 「 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最顶层的异常,为啥还多出一个any来表示捕获所有异常」?
❞
「只能说代码层面Exception是最顶层的,但是在字节码层面就不一样了。any表示哪些连Exception都无法处理的异常。」
再者,如果在方法签名上throws异常
代码
public void test() throws NullPointerException,FileNotFoundException,IOException{
try{
InputStream inputStream = new FileInputStream("test.cpp");
ServerSocket serverSocket = new ServerSocket(3306);
serverSocket.accept();
}catch (FileNotFoundException notFound){
}catch (IOException ioException){
}catch (Exception exception){
}finally{
System.out.println("finally");
}
}
多了点东西
总之,Java字节码对于异常的处理方式:
统一采用异常表的方式来对异常进行处理。 在jdk 1.4.2之前的版本中,并不是使用异常表的方式来对异常进行处理的,而是采用特定的指令方式。 当异常处理存在finally语句块时,现代化的JVM采取的处理方式是将finally语句块的字节码拼接到每一个catch块后面。 换句话说,程序中存在多少个catch块,就会在每一个catch块后面重复多少个finally语句块的字节码。