一、异常体系结构

5982616-88bc2c5c2d6ee0b9.webp

  • Throwable 类是 Java 语言中所有错误或异常的超类。
  • 只有当对象是此类(或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出。类似地,只有此类或其子类之一才可以是 catch 子句中的参数类型。
  • Throwable 包含了其线程创建时线程执行堆栈的快照。它还包含了给出有关错误更多信息的消息字符串。
  • 最后,它还可以包含 cause(原因):另一个导致此 throwable 抛出的 throwable。此 cause 设施在 1.4 版本中首次出现。它也称为异常链 设施,因为 cause 自身也会有 cause,依此类推,就形成了异常链,每个异常都是由另一个异常引起的。

    1、Error

  • Error 是 Throwable 的子类,用于指示合理的应用程序不应该试图捕获的严重问题

  • 大多数这样的错误都是异常条件。虽然 ThreadDeath 错误是一个“正规”的条件,但它也是 Error 的子类,因为大多数应用程序都不应该试图捕获它。
  • 在执行该方法期间,无需在其 throws 子句中声明可能抛出但是未能捕获的 Error 的任何子类,因为这些错误可能是再也不会发生的异常条件。
  • Java 程序通常不捕获错误。错误一般发生在严重故障时,它们在Java程序处理的范畴之外。

2、Exception

  • Exception 异常主要分为两类
  • 一类是 IOException(I/O 输入输出异常),其中 IOException 及其子类异常又被称作「受查异常」
  • 另一类是 RuntimeException(运行时异常),RuntimeException 被称作「非受查异常」。
  • 受查异常就是指,编译器在编译期间要求必须得到处理的那些异常,你必须在编译期处理了。

    2.1 常见的受检性异常

    image.png

    2.2 常见的非受检性异常

    image.png

    二、自定义异常

    Java 的异常机制中所定义的所有异常不可能预见所有可能出现的错误,某些特定的情境下,则需要我们自定义异常类型来向上报告某些错误信息。

  • 在 Java 中你可以自定义异常。编写自己的异常类时需要记住下面的几点。

    • 所有异常都必须是 Throwable 的子类。
    • 如果希望写一个检查性异常类,则需要继承 Exception 类。
    • 如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。

      三、异常处理方式

      1、try…catch关键字

      1. try{
      2. // 程序代码
      3. }catch(异常类型1 异常的变量名1){
      4. // 程序代码
      5. }catch(异常类型2 异常的变量名2){
      6. // 程序代码
      7. }catch(异常类型2 异常的变量名2){
      8. // 程序代码
      9. }

      2、throws/throw 关键字

  • 如果一个方法没有捕获一个检查性异常,那么该方法必须使用 throws 关键字来声明。throws 关键字放在方法签名的尾部。也可以使用 throw 关键字抛出一个异常,无论它是新实例化的还是刚捕获到的。

  • 下面方法的声明抛出一个 RemoteException 异常:
    1. public class className {
    2. public void deposit(double amount) throws RemoteException {
    3. // Method implementation
    4. throw new RemoteException();
    5. }
    6. //Remainder of class definition
    7. }
    一个方法可以声明抛出多个异常,多个异常之间用逗号隔开。

    四、其他

    1、OutOfMemoryError 可以被 try catch 吗?

    OutOfMemoryError 是可以 try catch 的。
    RuntimeException 是在程序运行中可能发生的异常,我们可以不捕获它,但可能带来 Crash 的代价,但是过多的捕获异常又不利于暴露和调试异常情况。在开发过程中,我们更多的应该及时暴露问题。除了 RuntimeException 以外,其他异常可以统称为 非运行时异常 或者 受检异常,这些异常必须被捕获,否则编译期就会报错。
    OutOfMemoryError 的父类们
    1. OutOfMemoryError <- VirtualMachineError <- Error
    OutOfMemoryError 是一个 Error ,Error 不应该被捕获。那么,捕获 OutOfMemoryError 有什么意义呢?
    如果你把捕获 OOM 当做处理 OOM 的一种手段,无疑是不合适的。你无法保证你 catch 的代码就是导致 OOM 的原因,可能它只是压死骆驼的最后一根稻草,甚至你也无法保证你的 catch 代码块中不会再次触发 OOM 。
    在 View.java 的 buildDrawingCacheImpl() 方法中有这么一段代码:
    1. try {
    2. bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(),
    3. width, height, quality);
    4. bitmap.setDensity(getResources().getDisplayMetrics().densityDpi);
    5. if (autoScale) {
    6. mDrawingCache = bitmap;
    7. } else {
    8. mUnscaledDrawingCache = bitmap;
    9. }
    10. if (opaque && use32BitCache) bitmap.setHasAlpha(false);
    11. } catch (OutOfMemoryError e) {
    12. // If there is not enough memory to create the bitmap cache, just
    13. // ignore the issue as bitmap caches are not required to draw the
    14. // view hierarchy
    15. if (autoScale) {
    16. mDrawingCache = null;
    17. } else {
    18. mUnscaledDrawingCache = null;
    19. }
    20. mCachingFailed = true;
    21. ......
    buildDrawingCacheImpl() 方法的大致作用是为当前 View 生成一个 Bitmap 缓存。在构建 Bitmap 对象的时候,如果捕捉到了 OOM ,就放弃生成 Bitmap 缓存,因为在 View 的绘制过程中 Bitmap Cache 并不是必须存在的
    所以在这里没有必要抛出 OOM ,而是自己捕获就可以了。
    在你自己明确知道可能发生 OOM 的情况下设置一个兜底策略,这可能是捕获 OOM 的唯一意义了

JVM 中哪一块内存不会发生 OOM ?
Java内存区域——堆,栈,方法区等
唯一一个在《Java虚拟机规范》中没有规定任何 OutOfMemoryError 情况的区域是 程序计数器。程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值则应为空(Undefined)。