异常定义
异常是指程序在执行过程中出现了意外情况,导致程序*中断运行。
1、Throwable、Exception、Error 区别
Throwable类是所有异常和错误的超类,其中Exception 和 Error都继承自Throwable类。 在 Java 中只有 Throwable 类型的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型。
Exception 和 Error 体现了 Java 平台设计者对不同异常情况的分类。
Exception 是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。
Exception 分为运行时异常和已检查异常:
运行时异常(RuntimeException),即不检查异常,通常由应用程序逻辑错误引起,应用程序应避免此类异常。如空指针、数组越界等。
已检查异常(Checked Exception)在代码中必须显示的进行捕获处理,这是编译期检查的一部分。假设是捕获了异常,然后恢复程序,但现实大多数情况下,都不能恢复。
Error 是程序无法处理的错误,表示运行应用程序中出现了严重的错误,一般会导致程序处于非正常的、不可恢复状态。JVM一般会选择线程终止,不便于也不需要捕获。如:虚拟机自身出现内存溢出(OutOfMemoryError)、栈溢出。
ClassNotFoundException 和 NoClassDefFoundError区别:
首先这两个类都是在类加载的过程中出现的异常。
ClassNotFoundException 类加载器在装载某个类的时候,在所有的类路径下都没有找到这个类,就会报ClassNotFoundException异常。这种情况下既使捕获了这个异常,也是无法恢复程序的,通常在执行下面的方法时容易抛出:
Class.forName()
ClassLoader.loadClass()
ClassLoader.findSystemClass()
也就是说,类加载器在加载阶段找不到类信息,此时如果直接采用反射或者类加载器的loadClass方法去动态加载一个所有classpath里面的都不存在的类,类加载器在运行时的load阶段就会直接抛出ClassNotFoundException异常。
NoClassDefFoundError 属于JVM的ERROR错误,严重级别较高。类加载器在使用阶段找不到类信息,即JVM编译时存在某个类,但是运行时却找不到,就出现了编译和运行不一致,所以就直接抛出了这个ERROR。比如类编译之后被删除了。
Java异常类层次结构图:
2、异常处理的基本原则
1)尽量不要捕获Exception这样的通用异常,而应该只捕获特定的异常。
try {
File file = new File("");
new FileInputStream(file);
} catch (FileNotFoundException e) {
// ...
}
2)不要生吞异常。
生吞异常会导致难以排查程序运行过程中出现的问题。或将异常抛出来,最好使用日志框架,详细地输出到日志系统中。
另外需要注意的地方:
尽量不要用一个大的try-catch包住整段的代码。
避免使用 e.printStackTrace() 抛出异常,对于分布式系统,如果发生异常,很难找到异常堆栈轨迹。
输出异常日志时,要考虑避免包含敏感信息,会导致潜在的信息安全问题。比如:用户数据、服务器配置等。
3、异常处理机制
异常捕获 try-catch-finally ,体现了JAVA语言的健壮性
抛出异常 throws 与 throw
两者的区别:
throw用来在方法体内抛出异常,而throws用来在方法声明处声明异常。
两者的联系:
如果一个方法中使用了throw关键字抛出了异常,那么要么立即用try/catch语句进行捕获,要么就是用throws进行声明,否则将出现编译错误。然而, 并不是只有使用了throw关键字之后才能使用throws关键字,语法上来说,任何一个方法都可以直接使用throws关键字,抽象方法也可以使用。
实际应用:
1)对于某些特定的业务发现异常情况时,根据自身业务情况,可以采取完善异常时的补偿机制;即当业务处理出现异常时,对当前业务多次进行重试请求。重试请求的策略根据业务的需要决定,当达到重试上限时依然无法成功,或放弃,或通过回调机制通知业务方执行失败。
2)异常信息要尽可能的全面,便于诊断业务异常过程。
异常处理带来的性能开销
1)try-catch 代码段会产生额外的性能开销,影响JVM对代码进行优化;
2)Java每实例化一个Exception,都会对当时的栈进行快照,这个操作相对比较重。如果异常非常频繁,这个开销就不能忽略了。频繁的Exception,也可能会导致服务反应变慢、吞吐量下降。