Java crash 异常分析

Java 异常

异常是指阻止当前方法或作用域继续执行的问题。当异常发生时,它会强制终止程序运行,记录异常信息并进行反馈。Java 为我们提供了非常完善的异常机制来保证程序的正常运行,其异常类层次结构如下图所示:

throwable

Throwable 是 Java 中所有异常的基类。它分为俩大类:Error(错误)和 Exception(异常)。

Error(错误):是程序无法处理的错误,大多数错误表示 JVM(Java 虚拟机)出现的问题,如 Java 虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。

Exception(异常):是程序本身可以处理的异常。例如 NullPointerException、IndexOutOfBoundException 等。

Java 的异常可以根据编译期间是否需要检查,分为受检异常和未受检异常:

  • 受检异常:是 RuntimeException 以外的 Exception 异常,类型上都属于 Exception 类及其子类。这些异常在编译期间就必须进行处理,否则会编译不通过。通过抛出受检的异常,强迫调用者在一个 catch 中处理该异常或传播出去,期望调用者能适当地恢复。
  • 未受检异常:不需要也不应该被捕获的可抛出结构,包含 Error 和 RuntimeException 异常。
    • 运行时异常:表明编程错误,是 RuntimeException 的子类,运行时检查。
    • 错误:表示资源不足,约束失败,或其他使程序无法继续执行的条件。

需要注意的是:异常是为了在异常情况下使用而设计的,不要将他们用于普通的控制流。其次,基于异常的循环模式不仅模糊了代码的意图,降低了性能( JVM 不会对异常的代码块进行优化),而且还不能保证正常工作。

处理异常

在 Java 中若出现可受检异常,则可以选择使用 try-catch-finally 捕捉异常,或重新抛出异常扔给上一级处理,不处理将会导致编译错误。对于未受检异常,一般由系统在运行时抛出,不需要去捕获或处理。

try-catch-finally 捕捉异常

  • try:捕获异常的代码块,监控可能产生异常的代码。其后可以接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
  • catch:处于 try 捕获到的异常。
  • finally:无论是否捕获到异常,finally 块都会被执行。一般把除内存之外的资源恢复到他们的初始状态。

抛出异常

throws

throws 总是出现在一个函数头中,用来表明该成员函数可能抛出的各种异常。由调用这个方法的上一级处理。

throw

throw 总是出现在函数体,用来抛出一个异常实例。程序会在 throw 语句后立即终止,它后面的语句执行不到。然后在包含它的所有 try 块中从里向外寻找含有其匹配的 catch 处理。

常见异常

NullPointException

接口传入空值

  • 在使用后台接口或调用外部 API 时,不要轻易相信别人做的空指针保护,null 很有可能是返回的结果之一。
  • json 解析的空值,使用 get 解析时会有 null 的情况。

在其他地方置空

如果使用的变量会在某些地方置空,这时需要注意在某些条件下,如延迟执行的任务里,可能会产生该变量的空指针异常。

IndexOutOfBoundsException

  • 单线程时,注意是否为空,长度是否大于0,是否有预判那么长
  • 多线程时,操作同一个对象,在未同步的情况下,clear时会导致另一个线程使用时越界

ConcurrentModificationException

多线程情况下,不同线程一边访问一边修改会造成并发访问异常。

案例:

  • 多线程情况下,直接返回成员变量,而不是一份新的赋值拷贝,很有可能在获取返回值后进行读取或修改时,其他地方也在操作这个成员变量,此时会造成并发访问的异常。