JDK8阅读笔记(二)java-lang:Error的子类

724 阅读12分钟

Error 是Throwable一个子类,它表示合理的应用程序不应尝试捕获的严重问题,大多数此类错误是异常情况。 ThreadDeath错误虽然是“正常”情况,但也是Error的子类,因此大多数应用程序不应该尝试捕获它。

一个方法不需要在它的throws子句中声明方法执行期间可能抛出但没有被捕获的Error任何子类,因为这些错误是不应该发生的异常情况。 也就是说,出于编译时检查异常的目的, Error及其子类被视为未经检查的异常。

Error存在多个子类,其中比较重要的两个是 VirtualMachineError 以及 LinkageError。


一、VirtualMachineError

相关源码

java.lang.VirtualMachineError 类抛出的异常表示 Java 虚拟机已损坏或已用完继续运行所需的资源。

abstract public class VirtualMachineError extends Error {
    private static final long serialVersionUID = 4161983926571568670L;

    public VirtualMachineError() {
        super();
    }

    /**
     * 使用指定的详细消息构造VirtualMachineError
     */
    public VirtualMachineError(String message) {
        super(message);
    }

    /**
     * 使用指定的详细消息和原因构造VirtualMachineError 。
     * 请注意,与cause关联的详细消息不会自动合并到此错误的详细消息中
     * @since  1.8
     */
    public VirtualMachineError(String message, Throwable cause) {
        super(message, cause);
    }

    /**
     * 使用指定的原因和(cause==null ? null : cause.toString())的详细消息构造一个VirtualMachineError
     * @since  1.8
     */
    public VirtualMachineError(Throwable cause) {
        super(cause);
    }
}

VirtualMachineError的类结构:

  • OutOfMemoryError 内存不足
  • StackOverflowError 堆栈溢出
  • InternalError 虚拟机内部错误
    • ZipError
  • UnknownError 未知错误

相关子类

  1. OutOfMemoryError异常

OutOfMemoryError 用于表示虚拟机内存资源不足,表示的几种内存相关的错误:

  • Java 堆空间 + 造成原因:
    • 无法在 Java 堆中分配对象
    • 吞吐量增加
    • 应用程序无意中保存了对象引用,对象无法被 GC 回收
    • 应用程序过度使用 finalizer。finalizer 对象不能被 GC 立刻回收。finalizer 由结束队列服务的守护线程调用,有时 finalizer 线程的处理能力无法跟上结束队列的增长 + 处理方法:
    • 添加 -Xmx
    • 修复应用程序中的内存泄漏
  • GC 开销超过限制
  • 请求的数组大小超过虚拟机限制 + 处理方法:
    • 添加 -Xmx
    • 修复应用程序中分配巨大数组的 bug
  • Permgen 空间 + 处理方法:
    • 添加 -XX: MaxPermSize
    • 重启
  • Metaspace:JDK8后,Perm gen 改成了 Metaspace + 处理方法:
    • 添加 -XX: MaxMetaSpaceSize
    • 取消 -XX: maxmetsspacedize
    • 减小 Java 堆大小
    • 修复 bug
  • 无法新建本机线程,表示内存不足,无法创建新线程
  • 杀死进程或子进程
  • 发生 stack_trace_with_native_method,表示native方法分配失败
  1. StackOverflowError异常

java.lang.StackOverflowError。通常由于执行程序中有一个错误,在线程重复递归调用同一个函数时会发生这个问题。线程的堆栈存储了执行的方法、基本数据类型值、局部变量、对象指针和返回值信息,所有这些都会消耗内存。如果线程的堆栈大小超过了内存分配限制,那么就会抛出

  1. InternalError 异常

抛出表示 Java 虚拟机中发生了一些意外的内部错误;JVM 抛出 java.lang.InternalError 有三个原因:

  • 虚拟机软件出现错误
  • 系统软件底层出现错误
  • 硬件出现故障。
  1. UnknownError 异常

当发生异常或错误,但 Java 虚拟机无法报告确切的异常或错误时,就会抛出 java.lang.UnknownError。


特征

VirtualMachineError 有两个主要特征:

  • 非受检异常(Unchecked exceptions)
  • 同步模式与异步模式
  1. 非受检异常

在编译时检查的异常称为受检异常。如果代码中的某些方法抛出受检异常,那么该方法必须处理该异常或者使用 throws 关键字指定异常。受检异常包括 IOException、SQLException、DataAccessException、ClassNotFoundException 等。

非受检异常常没有这个要求,它们不需要捕获或者声明抛出。所有类型的 VirtualMachineError 都是非受检异常。

  1. 同步模式与异步模式

同步异常在特定程序语句执行时发生,无论该程序在类似的环境中执行了多少次。同步异常的例子有 NullPointerException、 ArrayIndexOutOfBoundException 等

异步异常可以在任何时间点和程序语句的任何部分发生,异常抛出的地方也不一样。

  • 所有的 VirtualMachineError 都是异步抛出的,但有时也会同步抛出;
  • StackOverflowError 可能随方法调用而同步抛出,也可能随着本地方法执行或 Java 虚拟机资源限制异步抛出;
  • OutOfMemoryError 可能在对象创建、数组创建、类初始化和装箱转换时同步或异步抛出。

二、LinkageError

什么是 LinkageError 错误

java.lang.LinkageError 错误 发生场景:

当一个类对另一个类有某种依赖,但在编译前一个类后,后一个类发生了不兼容的变化。在多ClassLoader的代码中会出现。

相关源码:

public class LinkageError extends Error {
    private static final long serialVersionUID = 3579600108157160122L;
    public LinkageError() {
        super();
    }
    public LinkageError(String s) {
        super(s);
    }
    public LinkageError(String s, Throwable cause) {
        super(s, cause);
    }
}

错误发生的原因:

  • 同一个限定名的class类被多个不同的ClassLoader加载后,相互交叉使用导致的类冲突的情况。同一个限定名的class在不同的classLoader中属于不同的 Class实例,而JVM在加载某一个类时,需要加载所有import进入的Class,这种情况下,如果自定义的classLoader中存在与parentClassLoader需要加载相同限定名的Class时,就会抛出java.Lang.LinkageError.

避免方法:

  • 在使用多个包进行编码时,需要将出现相同依赖的包进行排除,避免同时加载多个同限定名的class。

LinkageError相关类的结构:

  • LinkageError + ClassFormatError
    • GenericSignatureFormatError
    • ClassFormatError + BootstrapMethodError + ClassCircularityError + ExceptionInInitializerError + IncompatibleClassChangeError
    • AbstractMethodError
    • IllegalAccessError
    • InstantiationError
    • NoSuchFieldError
    • NoSuchMethodError + NoClassDefFoundError + UnsatisfiedLinkError + VerifyError

在类加载阶段中,不同阶段会报不同的错误,通过这些一脉相承的错误类,可以一探类加载的流程。


类加载阶段的错误

类加载过程分三个阶段:

  • 加载
  • 链接 + 验证
    • 文件格式验证
    • 元数据验证
    • 字节码验证
    • 符号引用验证 + 准备 + 解析
  • 初始化
  1. 加载阶段的错误
  • 本阶段虚拟机需要先获取类或接口的名称,这个信息可以在二进制类文件格式里找到,定义该类并创建java.lang.Class对象;如果找不到,该阶段会抛出异常 NoClassDefFound。另外,在本阶段格式检查会检测语法问题,如果有错会抛出 ClassFormatError(文件格式错误/无法解释为类文件) 或 UnsupportedClassVersionError(版本不一致)

  • 在加载类之前,VM会先加载超类或超类接口。如果类的层级有问题,比如该类是自己的超类或超类接口(递归),VM将会抛出java.lang.ClassCircularityError错误。

  • 如果直接超类接口是个不是接口类型或者直接超类是一个接口,将会抛出 IncompatibleClassChangeError 错误。

  1. 链接阶段的错误

链接阶段可以分为三个子阶段:验证、准备、解析

2.1 验证

JVM 需要核验字节信息是符合 Java 虚拟机规范,否则就被认为是 VerifyError,用于防止恶意信息或者合规的信息危害 JVM 的运行。

验证流程:

  • 文件格式验证 (验证字节流是否符合Class 文件格式的规范,并且能被当前版本的虚拟机处理)
  • 元数据验证 (对字节码描述信息进行语义分析)
  • 字节码验证 (通过数据流和控制流分析语义,如VerifyError)
  • 符号引用验证(对常量池中的各种符号引用的信息进行匹配性校验,如IncompatibleClassChangeError)

java.lang.VerifyError 表示验证失败,大部分验证失败都会报这个错误;而符号引用验证失败会抛出特定的异常:

  • AbstractMethodError:表示调用的抽象方法
  • IllegalAccessError:Method 对象访问private或者protected成员变量或者方法所致
  • InstantiationError:使用 Java new构造实例化抽象类或接口时抛出
  • NoSuchFieldError:定义字段不存在
  • NoSuchMethodError:定义方法不存在

2.2 准备

创建类或接口中的静态变量,并初始化静态变量的初始值。这里的“初始化”侧重点在于分配所需要的内存空间,之后才会去执行进一步的 JVM 指令

2.3 解析

将常量池中的符号引用(symbolic reference)替换为直接引用。可能会抛出以下异常:

java.lang.UnsatisfiedLinkError 如果 Java 虚拟机无法声明为 native的方法装链接到一个不存在的本机库时,会抛出该异常

  • java.lang.BootstrapMethodError 表明 invokedynamic指令未能找到它的启动方法,或启动方法未能提供与目标方法类型一致的调用点;这种异常通常是因为jar包版本冲突而引起的

  • java.lang.NoClassDefFoundError 当JVM在加载一个类的时候,如果这个类在编译时是可用的,但是在运行时找不到这个类的定义的时候,JVM就会抛出一个NoClassDefFoundError错误

  1. 初始化阶段可能存在的错误

初始化阶段会执行静态初始化器,以及为静态字段进行初始化。在这里,java 代码第一次被执行,类的初始化是要超类初始化的,尽管超类接口不初始化。

  • java.lang.ExceptionInInitializerError 表示静态初始化程序中发生意外异常的信号。 抛出ExceptionInInitializerError以指示在评估静态初始化程序或静态变量的初始化程序期间发生异常

三、其它Error的子类

在jdk8中还有其它的Error类:

  • AWTError (java.awt)
  • AnnotationFormatError (java.lang.annotation)
  • AssertionError (java.lang)
  • StateInvariantError (javax.swing.text)
  • CoderMalfunctionError (java.nio.charset)
  • VirtualMachineError (java.lang)
  • ServiceConfigurationError (java.util)
  • CompilerError (sun.tools.java)
  • TokenMgrError (com.sun.jmx.snmp.IPAcl)
  • LinkageError (java.lang)
  • AgentConfigurationError (sun.management)
  • ReflectionError in Reflect (sun.nio.ch)
  • IOError (java.io)
  • ThreadDeath (java.lang)
  • ServiceConfigurationError (sun.misc)
  • TokenMgrError (com.sun.tools.example.debug.expr)

java.lang.AssertionError

AssertionError抛出表示断言失败。该类提供的七个单参数公共构造函数确保调用返回的断言错误:new AssertionError(expression)

java.lang.annotation.AnnotationFormatError

当注释解析器尝试从类文件中读取注释并确定注释格式错误时抛出AnnotationFormatError,用于反射读取注释的API可以抛出此错误

java.lang.ThreadDeath

调用 Thread 类中带有零参数的 stop 方法时,受害线程将抛出一个 ThreadDeath 实例。仅当应用程序在被异步终止后,又必须清除时才应该捕获这个类的实例。如果 ThreadDeath 被一个方法捕获,那么将它重新抛出非常重要,因为这样才能让该线程真正终止。果不抓取Error的话,是不会有任何错误信息。

public class ThreadDeath extends Error {
    private static final long serialVersionUID = -4417128565033088268L;
}

java.util.ServiceConfigurationError

加载服务提供者时出现问题时抛出的错误ServiceConfigurationError,在以下情况下会抛出此错误:

  • 提供者配置文件的格式违反了规范;
  • 读取提供者配置文件时发生IOException ;
  • 找不到在提供者配置文件中命名的具体提供者类;
  • 具体的提供者类不是服务类的子类;
  • 一个具体的提供者类不能被实例化;
  • 发生了一些其他类型的错误。
public class ServiceConfigurationError
    extends Error {
    private static final long serialVersionUID = 74132770414881L;
    public ServiceConfigurationError(String msg) {
        super(msg);
    }
    public ServiceConfigurationError(String msg, Throwable cause) {
        super(msg, cause);
    }
}

java.io.IOError

当发生严重的 I/O 错误时抛出 IOError,相关源码如下:

public class IOError extends Error {
    public IOError(Throwable cause) {
        super(cause);
    }
    private static final long serialVersionUID = 67100927991680413L;
}

java.nio.charset.CoderMalfunctionError

当CharsetDecoder的decodeLoop方法或者CharsetEncoder的encodeLoop方法出现不可预知错误时抛出 CoderMalfunctionError。

public class CoderMalfunctionError
    extends Error {
    private static final long serialVersionUID = -1151412348057794301L;

    public CoderMalfunctionError(Exception cause) {
        super(cause);
    }
}

javax.swing.text.StateInvariantError

StateInvariantError错误用于报告所做的状态不变断言失败,这表明发生了内部错误。

class StateInvariantError extends Error
{
    public StateInvariantError(String s) {
        super(s);
    }

}

java.awt.AWTError

AWTError错误在发生严重的Abstract Window Toolkit错误时抛出。

public class AWTError extends Error {
    private static final long serialVersionUID = -1819846354050686206L;
    public AWTError(String msg) {
        super(msg);
    }
}

sun.nio.ch.Reflect.ReflectionError

ReflectionError 用于表示反射错误

class Reflect {
    private static class ReflectionError extends Error {
        private static final long serialVersionUID = -8659519328078164097L;

        ReflectionError(Throwable var1) {
            super(var1);
        }
    }
}

sun.management.AgentConfigurationError

AgentConfigurationError 用于管理代理抛出的配置错误,相关源码如下:

public class AgentConfigurationError extends Error {
    public static final String AGENT_EXCEPTION =
        "agent.err.exception";
    public static final String CONFIG_FILE_NOT_FOUND    =
        "agent.err.configfile.notfound";
    ......
    private final String error;
    private final String[] params;

    public AgentConfigurationError(String error) {
        super();
        this.error = error;
        this.params = null;
    }

    public AgentConfigurationError(String error, Throwable cause) {
        super(cause);
        this.error = error;
        this.params = null;
    }

    public AgentConfigurationError(String error, String... params) {
        super();
        this.error = error;
        this.params = params.clone();
    }

    public AgentConfigurationError(String error, Throwable cause, String... params) {
        super(cause);
        this.error = error;
        this.params = params.clone();
    }

    public String getError() {
        return error;
    }

    public String[] getParams() {
        return params.clone();
    }

    private static final long serialVersionUID = 1211605593516195475L;
}

该类定义了大量代理类的路径常量,会在抛出该异常时调用。

sun.tools.java.CompilerError

内部编译器发生错误时会引发 CompilerError 异常警告:此源文件的内容不属于任何受支持的 API。 依赖于它们的代码自行承担风险:它们可能会更改或删除,恕不另行通知。

public class CompilerError extends Error {
    Throwable e;

    public CompilerError(String msg) {
        super(msg);
        this.e = this;
    }

    public CompilerError(Exception e) {
        super(e.getMessage());
        this.e = e;
    }

    public void printStackTrace() {
        if (e == this)
            super.printStackTrace();
        else
            e.printStackTrace();
    }
}

sun.misc.ServiceConfigurationError

ServiceConfigurationError 表示查找服务提供商时出现问题时抛出的错误。可能抛出该错误的情况:

  • 找不到具体的提供者类
  • 一个具体的提供者类不能被实例化
  • 提供者配置文件的格式是非法的
  • 读取提供程序配置文件时发生 IOException。

相关源码

public class ServiceConfigurationError extends Error {
    static final long serialVersionUID = 8769866263384244465L;
    public ServiceConfigurationError(String msg) {
        super(msg);
    }
    public ServiceConfigurationError(Throwable x) {
        super(x);
    }

}