第2讲 Exception和Error有什么区别?

请对比 Exception 和 Error,另外,运行时异常与一般异常有什么区别?

0. 历史因素和使用习惯:

Checked Exception设计的初衷是要让程序去处理异常,从异常恢复。但是实际情况是大部分情况下都不可能恢复,已经偏离了初衷。
另外,在函数式编程中,如果用了检查型异常,则需要在用lambda表达式的地方都声明异常,与lambda表达式使程序简洁的目的违背,所以spring等框架不采用Checked Exception,异常都设计为不检查异常。 kotlin里面就是没有checked Exception。
Java 语言的 Checked Exception 也许是个设计错误。

1. 异常分类

ExceptionError 都是继承了 Throwable,Exception表示异常,可以被捕获,Error表示错误,Error一般都来自JVM虚拟机,程序无法从错误中恢复。

Exception分为检查型(check)异常非检查型(uncheck)异常

  • 检查型异常在编译期就要检查,注意IOException属于检查型异常;
  • 非检查型异常就是在运行时期才可能发生的异常,比如NullPointerException,运行时的异常可以在编码中规范约束,也可以选择抛出异常并处理。

<<JAVA核心技术 卷Ⅰ>> 第9版,pp.474 "JAVA语言规范将派生于Error类或RuntimeException类的所有异常称为未检查(unchecked)异常,所有其他的异常称为已检查(checked)异常。"

2. 了解常用RuntimeException的类

3. 使用异常的注意事项:

  • Throw early catch late
  • 尽量不要捕获类似 Exception 这样的通用异常,而是应捕获特定异常
  • 不要生吞异常,这会造成无法排查问题
  • 生产环境避免使用printStackTrace()。Java 每实例化一个 Exception,jvm会调用fillstacktrace本地方法对当时的线程栈进行快照,比较重的操作。
  • 避免使用try catch做流程跳转

当我们的服务出现反应变慢、吞吐量下降的时候,检查发生最频繁的 Exception 也是一种思路。

4. 经典题:

NoClassDefFoundError 和 ClassNotFoundException 有什么区别?

ClassNotFoundException是在写编码的时候编译器就能告诉你这个地方需要捕获异常,如:使用Class.forName的时候就必须要你捕获或者throws这个异常,主要发生在反射或者loadclass()时。

而NoClassDefFoundError在Javac已经把程序成功的编译成字节码文件了,当JVM进程启动,通过类加载器加载字节码文件,然后由JIT去编译字节码指令的时候,运行时,在classpath下找不到对应的类,比如说某个class被误删除了。这种错误有可能是项目中的操作配置不当造成的。

5. 思考题:

现在非常火热的反应式编程(Reactive Stream),因为其本身是异步、基于事件机制的,所以出现异常情况,决不能简单抛出去;另外,由于代码堆栈不再是同步调用那种垂直的结构,这里的异常处理和日志需要更加小心,我们看到的往往是特定 executor 的堆栈,而不是业务方法调用关系。对于这种情况,你有什么好的办法吗?

“尤其是对于分布式系统,如果发生异常,但是无法找到堆栈轨迹(stacktrace),这纯属是为诊断设置障碍。所以,最好使用产品日志,详细地输出到日志系统里。”

评论回复:
1)异常:这种情况下的异常,可以通过完善任务重试机制,当执行异常时,保存当前任务信息加入重试队列。重试的策略根据业务需要决定,当达到重试上限依然无法成功,记录任务执行失败,同时发出告警。
2)日志:类比消息中间件,处在不同线程之间的同一任务,简单高效一点的做法可能是用traceId/requestId串联。有些日志系统本身支持MDC/NDC功能,可以串联相关联的日志。


扩展

1. 根据抛异常时new exception对象,jvm会去snapshot线程栈fillstacktrace,fillstacktrace是本地方法,影响性能这一点,问:文中并未明确说异常对性能的影响,看了Throwal中的方法并未提及性能问题,有具体的抛出异常情况与通过返回码返回方式性能差异对比吗?
回复:请看Shipilev的分析吧:https://shipilev.net/blog/2014/exceptional-performance/

2. RxJava中有一些异常处理的方法 比如doOnError switchOnError 可以选择把异常包起来再抛出去来保留上下文

3. 问:我记得栈帧里面异常块与代码块是分离的,如果不出现异常好像是不影响性能的吧...
作者回复: 也会影响优化

4. Spring cloud sleuth可以解决调用跟踪问题,还有一些APM项目

5. 问:能不能讲下怎么捕捉整个项目的全局异常
评论:关于捕获全局异常,Spring MVC的方式就很实用;可以考虑使用AOP技术在接口入口层统一捕获,特别是使用类似dubbo这样的非spring mvc架构的系统非常有用。

6. 了解堆栈快照 try catch finally等细节,jvm规范第三章有细节,也可以自己写一段程序,用javap反编译看看 goto、异常表等等


笔记整理自极客时间《Java核心技术36讲》专栏,仅做学习用途。
《Java核心技术36讲》 :前Oracle首席工程师,讲解Java核心技术原理 、 Java面试必考知识点 、 完整的Java知识体系 ,有深度有广度。
极客时间专栏《Java核心技术36讲》

发布者

Jiaheng Tao

挖掘概念,创造工具