深入拆解Java虚拟机
郑雨迪
Oracle 高级研究员,计算机博士
立即订阅
27947 人已学习
课程目录
已完结 39 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 为什么我们要学习Java虚拟机?
免费
模块一:Java虚拟机基本原理 (12讲)
01 | Java代码是怎么运行的?
02 | Java的基本类型
03 | Java虚拟机是如何加载Java类的?
04 | JVM是如何执行方法调用的?(上)
05 | JVM是如何执行方法调用的?(下)
06 | JVM是如何处理异常的?
07 | JVM是如何实现反射的?
08 | JVM是怎么实现invokedynamic的?(上)
09 | JVM是怎么实现invokedynamic的?(下)
10 | Java对象的内存布局
11 | 垃圾回收(上)
12 | 垃圾回收(下)
模块二:高效编译 (12讲)
【工具篇】 常用工具介绍
13 | Java内存模型
14 | Java虚拟机是怎么实现synchronized的?
15 | Java语法糖与Java编译器
16 | 即时编译(上)
17 | 即时编译(下)
18 | 即时编译器的中间表达形式
19 | Java字节码(基础篇)
20 | 方法内联(上)
21 | 方法内联(下)
22 | HotSpot虚拟机的intrinsic
23 | 逃逸分析
模块三:代码优化 (10讲)
24 | 字段访问相关优化
25 | 循环优化
26 | 向量化
27 | 注解处理器
28 | 基准测试框架JMH(上)
29 | 基准测试框架JMH(下)
30 | Java虚拟机的监控及诊断工具(命令行篇)
31 | Java虚拟机的监控及诊断工具(GUI篇)
32 | JNI的运行机制
33 | Java Agent与字节码注入
模块四:黑科技 (3讲)
34 | Graal:用Java编译Java
35 | Truffle:语言实现框架
36 | SubstrateVM:AOT编译框架
尾声 (1讲)
尾声 | 道阻且长,努力加餐
深入拆解Java虚拟机
登录|注册

06 | JVM是如何处理异常的?

郑雨迪 2018-08-01
今天我们来讲讲 Java 虚拟机的异常处理。首先提醒你一下,本篇文章代码较多,你可以点击文稿查看具体代码。
众所周知,异常处理的两大组成要素是抛出异常和捕获异常。这两大要素共同实现程序控制流的非正常转移。
抛出异常可分为显式和隐式两种。显式抛异常的主体是应用程序,它指的是在程序中使用“throw”关键字,手动将异常实例抛出。
隐式抛异常的主体则是 Java 虚拟机,它指的是 Java 虚拟机在执行过程中,碰到无法继续执行的异常状态,自动抛出异常。举例来说,Java 虚拟机在执行读取数组操作时,发现输入的索引值是负数,故而抛出数组索引越界异常(ArrayIndexOutOfBoundsException)。
捕获异常则涉及了如下三种代码块。
try 代码块:用来标记需要进行异常监控的代码。
catch 代码块:跟在 try 代码块之后,用来捕获在 try 代码块中触发的某种指定类型的异常。除了声明所捕获异常的类型之外,catch 代码块还定义了针对该异常类型的异常处理器。在 Java 中,try 代码块后面可以跟着多个 catch 代码块,来捕获不同类型的异常。Java 虚拟机会从上至下匹配异常处理器。因此,前面的 catch 代码块所捕获的异常类型不能覆盖后边的,否则编译器会报错。
finally 代码块:跟在 try 代码块和 catch 代码块之后,用来声明一段必定运行的代码。它的设计初衷是为了避免跳过某些关键的清理代码,例如关闭已打开的系统资源。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《深入拆解Java虚拟机》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(40)

  • godtrue
    感谢雨迪!
    这节让我终于搞清楚了两个疑惑!

    1:使用异常捕获的代码为什么比较耗费性能?
    因为构造异常的实例比较耗性能。这从代码层面很难理解,不过站在JVM的角度来看就简单了,因为JVM在构造异常实例时需要生成该异常的栈轨迹。这个操作会逐一访问当前线程的栈帧,并且记录下各种调试信息,包括栈帧所指向方法的名字,方法所在的类名、文件名,以及在代码中的第几行触发该异常等信息。
    虽然具体不清楚JVM的实现细节,但是看描述这件事情也是比较费时费力的。

    2:finally是怎么实现无论异常与否都能被执行的?
    这个事情是由编译器来实现的,现在的做法是这样的,编译器在编译Java代码时,会复制finally代码块的内容,然后分别放在try-catch代码块所有的正常执行路径及异常执行路径的出口中。


    2018-08-02
    4
    99
  • 东方
    throw exception性能差fillstacktrace除了遍历堆栈以外,如果有inline 代码消除等编译优化发生,是不是要先“去优化”完了再fill?要不然可能出现错误堆栈和代码对不上的情况

    throw exception估计也会影响jit的优化,进而影响整体性能

    作者回复: 思考得很深,赞一个!

    即时编译器生成的代码会保存原始的栈信息,以便去优化时能够复原。fillStackTrace也会读取这些信息的,所以不用先去优化再fill。

    抛异常本身带来了额外的执行路径。通常如果能够将异常处理器也编译进去,那么不会有太大影响。

    2018-08-11
    1
    19
  • 阿坤
    如果finally有return语句,catch内throw的异常会被忽略,这个从jvm层面怎么解释呢?

    作者回复: catch里抛的异常会被finally捕获了,再执行完finally代码后重新抛出该异常。由于finally代码块有个return语句,在重新抛出前就返回了。

    你可以利用这篇文章的知识,就着javap的输出,分析一下具体的程序路径

    2018-09-02
    18
  • 三木子
    看完今天的文章有几个疑问

       1方法的异常表是包含RuntimeException这种非check类型的异常吧?如果是那么每个方法都有异常表,那么是不是每个异常表中都有像ArrayIndexOutOfBoundsException这类型异常了。这类公共异常是私有还是共享呢

       2像catch自定义异常,也会添加的当前方法的异常表里吗?

       3 我们常常看到的异常调用栈,这里方法调用信息其实就是弹出方法栈帧吗?

    作者回复: 1 检查异常这个概念只在源代码中出现。异常表不是声明这段代码所有有可能抛出的异常,而是声明会被捕获的异常。

    2 会的

    3 栈轨迹 跟 弹出方法栈帧 是两个概念。你可以直接新建一个异常,然后不抛出,直接打印调用栈。这个时候是不会弹出当前栈帧的。

    2018-08-01
    1
    6
  • Krloy
    关于try catch的疑问

    如果for里面中写 try catch 一百条数据中有1条数据异常 程序正常执行 会返回99条数据
    如果for里面不写 try catch写外面 程序正常执行 但是数据返回0

    try catch 异常实例构造非常昂贵,因为虚拟机会生成改异常的栈轨迹,改操作会逐一访问改线程栈帧,并记录下各种调试信息。

    那么如果我在for中写try catch 的话 会不会每次循环都生成一个异常实例?
    上面两种写try catch的方法 哪种要更好点
    2018-08-08
    1
    5
  • 李双迎
    老师,如果异常构造比较耗时,那么能否通过缓存同一位置相同异常的实例,来解决呢?

    作者回复: 理论上是可以的,一般不这么做,原因有两个,一是异常路径无需考虑性能,二是代码可读性。

    2018-09-07
    3
  • 吴伟
    检查异常和非检查异常也就是其他书籍中说的编译期异常和运行时异常?

    作者回复: 编译期异常和运行时异常这种划分有点奇怪。

    检查异常也会在运行过程中抛出。但是它会要求编译器检查代码有没有显式地处理该异常。非检查异常包括Error和RuntimeException(会不会那本书直译为”运行时异常”?),这两个则不要求编译器显式处理。

    2018-08-01
    3
  • 王小臭
    辛苦老师了,这么早更新
    2018-08-01
    3
  • 孤独患者
    如果在业务层的代码中使用Assert来判断参数是否有问题,然后在调用方捕捉异常,这样会不会耗性能

    作者回复: 首先走抛出异常捕获异常的异常执行路径的话,性能肯定是很慢的,因此最好在参数出现问题的概率很小的情况下使用这种方式。

    另外,你说的Assert是某个库的工具类,还是assert语句?后者的话,一般只在开发环境中启用吧。

    2018-08-01
    2
  • 我已经设置了昵称
    对于实践环节表示看不懂字节码代码,无法理解,老师能不能在后篇解释下前篇遗留的问题
    2019-03-14
    1
  • gentleman♥️
    就是checked异常 一直不try catch ,jvm会怎么个处理流程呢
    2019-01-08
    1
  • MissSunday
    这一篇是看的明白的的一篇。😂
    2018-11-28
    1
  • Geek_987169
    老师,请教您一个问题,jvm在执行字节码指令的过程中,在什么情况下会由顺序执行变为跳转执行?
    2018-09-12
    1
  • Ennis LM
    Java 虚拟机会忽略掉异常构造器以及填充栈帧的 Java 方法(Throwable.fillInStackTrace),直接从新建异常位置开始算起。
    Java 虚拟机还会忽略标记为不可见的 Java 方法栈帧。

    请问老师,填充栈帧的 Java 方法和不可见的 Java 方法栈帧,是什么

    作者回复: 前者指Throwable.fillStackTrace以及异常的构造器,后者为Java虚拟机不想让用户看到的栈帧,比如说方法句柄的适配器类中的方法。之后讲Lambda时会有具体的例子。

    2018-08-01
    1
  • 南城风戈
    沙发
    2018-08-01
    1
  • 脸皮
    文中说"该异常对应的栈轨迹并非 throw 语句的位置,而是新建异常的位置。"我的问题是新建异常的位置在哪里?怎么理解?请老师回答一下谢谢😜
    2019-12-08
  • 兔子
    老师,您好!java.lang.Error这种错误产生的原因是什么样的?jvm对这种Error的处理方式跟Exception一样的吗?如果程序碰到这种情况为了确保程序还能正常运行加上try catch是否就可以了?谢谢!

    作者回复: 应该反过来思考。当碰见没法确保程序正常运行的时候,应用程序应当抛error。否则抛(checked) exception便可以了。

    2019-11-27
  • Randy
    郑老师,请教一下 ,文章中说下面这段代码编译出了3份finally 代码块,请问是怎么看出来的,请帮忙解读一下
    public void test() {
        try {
          tryBlock = 0;
        } catch (Exception e) {
          catchBlock = 1;
        } finally {
          finallyBlock = 2;
        }
        methodExit = 3;
      }

    作者回复: 你就看看字节码里面出现几句iconst_2 (由finallyBlock = 2编译得出)

    2019-11-21
  • gogo
    老师您好,请教一个问题,在spring项目中使用了统一异常处理,在service层做一些校验,校验失败时抛出异常,在统一异常处理逻辑里封装异常信息返回给客户端,这种场景自定义异常集成RuntimeException是不是比较好呢?
    2019-09-27
  • 随心而至
    赞,要通过工具来验证到底是不是这样,比如try-catch-finally构造例子来验证那段很赞。
    2019-09-26
收起评论
40
返回
顶部