Java中的ExceptionInInitializerError异常及解决方法

本文深入探讨了Java中的ExceptionInInitializerError异常,解释了其产生的原因,包括静态初始化块中的异常处理,以及如何诊断和解决此类问题。

当在静态初始化块中出现了异常的时候,JVM会抛出 java.lang.ExceptionInInitializerError异常。如果你了解Java中的静态变量,你会知道它们是在类加载的时候进行初始化的。如果在这个静态变量初始化的过程中出现了异常,那么就会抛出 java.lang.ExceptionInInitializerError异常。

任何异常都可能会引发这种情况,比如说,java.lang.ArrayIndexOutOfBound或者java.lang.NullPointerException。Java开发人员通常会被这个错误弄晕,他觉得自己并没有定义任何的静态初始化块,为什么还会抛出ExceptionInInitializerError异常;

事实上,Java默认会将静态变量的初始化放在一个默认的静态初始化块中,然后按它们在源文件中声明的顺序来进行初始化。比如说变量ABC声明在第一行,在第二行中使用到了,而在第三行的时候才初始化,那么第二行的代码会抛出一个NullPointerException异常,这个异常会被封装到一个ExceptionInInitializerError异常中,如果这段代码在主线程中执行了,你会看到控制台或者日志文件中出现这样的错误信息:

"Exception in thread "main" 
java.lang.ExceptionInInitializerError"。

在一个拥有大量日志文件的大型系统中,这样的错误很容易被忽略,而程序员会得到一个java.lang.NoClassDefFoundError异常。不幸的是只有当别人使用到了这个类的时候才会出现这个错误,因为ExceptionInInitializerError导致了这个类无法加载。由于类加载失败了,因此JVM会抛出NoClassDefFoundError。有的时候这会误导Java开发人员,他们会检查类路径,PATH,以及java.library.path看是不是缺少了这个类,却又发现不了任何问题,这让他们很困惑。

如果你在分析NoClassDefFoundError的原因,你最好看下你的日志文件中有没有ExceptionInInitializerError,然后再考虑要不要检查classpath。本文中我们将看到一段代码,它会在静态初始化过程中引发异常从而导致 “Exception in thread “main” java.lang.ExceptionInInitializerError”。在稍后的部分,我们将会看到如何去解决这个问题。

Exception in thread “main”java.lang.ExceptionInInitializerError的原因 正如别的错误或者异常一样,当你看见这行信息,你知道这是出现ExceptionInInitializerError异常了,这个异常是由于类加载过程中静态块初始化过程失败所导致的。由于它出现在负责启动程序的主线程中,因此你最好从主类中开始分析,这里说的主类是指你在命令行参数中指定的那个,或者说是你声明了
public static void main(String args[])方法的那个类。如果你仔细地看一下完整的堆栈跟踪信息,你其实什么也不用做,因为JVM已经把类名给打印出来了,这就是引发ExceptionInInitializerError的类。

ExceptionInInitializerError是LinkageError的子类,这意味着这个异常会导致你的类无法加载到JVM的内存中。现在我们来看一下这个示例程序,它在执行的时候会抛出下面的异常:

Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
        at java.util.ArrayList.rangeCheck(ArrayList.java:635)
        at java.util.ArrayList.get(ArrayList.java:411)
        at StaticInitiazerDemo.<clinit>(StaticInitiazerDemo.java:15)

看一下栈跟踪信息,你知道真正的异常是java.lang.IndexOutOfBoundsException,它在StaticInitiazerDemo的第二行被抛出来了。这是由于你调用了ArrayList的get()方法并传入了位置0,而这个ArrayList的大小也是0(Index: 0, Size: 0)。看到这条信息后你知道当我们想从列表中取出第一张CreditCard时,这个列表是空的。

import java.util.ArrayList;
import java.util.List;

/**
 * Java Program to understand and solve ExceptionInitializerError, which comes
 * When static initializer blocks throws unchecked exception during class loading
 * and initialization.
 *
 * @author Javin Paul
 */

public class StaticInitializerDemo{

    private static final List<CreditCard> cards = new ArrayList<CreditCard>();
    private static CreditCard prefferdCard = cards.get(0); // 1st card is default
    public static boolean isVisa = "VISA".equalsIgnoreCase(prefferdCard.getNetwork());

    public static void main(String args[]) {

        makePayment(prefferdCard);

    }

    public static void makePayment(CreditCard cc) {
        if (isVisa) {
            //offer 5% discount
        }
        // deduct payment
    }

}

class CreditCard {

    private long card_number; //16 digit card number
    private int cvv; // 3 digit cvv number
    private int expiryMonth;
    private int expiryYear;
    private String bank;
    private String network;

    public CreditCard(long card_number, int cvv, int expiryMonth, int expiryYear, String bank, String network) {
        super();
        this.card_number = card_number;
        this.cvv = cvv;
        this.expiryMonth = expiryMonth;
        this.expiryYear = expiryYear;
        this.bank = bank;
        this.network = network;

    }

    /**
     * @return the card_number
     */
    public final long getCard_number() {
        return card_number;
    }
    /**
     * @return the cvv
     */
    public final int getCvv() {
        return cvv;
    }
    /**
     * @return the expiryMonth
     */
    public final int getExpiryMonth() {
        return expiryMonth;
    }
    /**
     * @return the expiryYear
     */
    public final int getExpiryYear() {
        return expiryYear;
    }
    /**
     * @return the bank
     */
    public final String getBank() {
        return bank;
    }
    /**
     * @return the network
     */
    public final String getNetwork() {
        return network;
    }
}

输出:

Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
    at java.util.ArrayList.rangeCheck(Unknown Source)
    at java.util.ArrayList.get(Unknown Source)
    at StaticInitializerDemo.<clinit>(StaticInitializerDemo.java:15)

这里是Java中所有Error类的类结构。你可以看到ExceptionInInitializerError是继承自LinkageError的。还应当知道的是,像RuntimeException一样,Error也是未检查异常,编译器是不去检查有没有相应的异常处理代码的。

如何解决Exception in thread “main” java.lang.ExceptionInInitializerError

  1. “Exception in thread “main” java.lang.ExceptionInInitializerError”意味着异常出现在主线程,并且是LinkageError的一个子类java.lang.ExceptionInInitializerError,这是JVM类加载失败时才抛出的,原因是静态初始化代码中出现了诸如IndexOutOfBoundsException或者NullPointerException这样的RuntimeException。
  2. 记住JVM会将所有的静态变量的初始化按它们在源文件中的出现顺序放到一个静态初始化块中。因此,不要觉得没有看到静态初始块就认为不会出现这个异常。事实上,你得确保静态变量的正确顺序,比如说,如果 一个变量初始化的时候用到了另一个变量,你得确保这个变量在前面已经初始化过了。
  3. 如果别的代码想要使用这个类,则会抛出ExceptionInInitializerError异常,而它又会导致ClassNotFoundException或者NoClassDefFoundError。为什么?因为这个类加载失败了,并没有加载到JVM的内存中。因此如果你在解决类不存在之类的异常时,先看看你的日志文件中有没有这个异常。
  4. 记住静态初始化代码块会抛出RuntimeException而不是已检查异常,而后者需要有对应的catch块来进行处理。

这就是关于Exception in thread “main” java.lang.ExceptionInInitializerError的所有东西了。你已经了解到了如何去跟踪此类问题,并找出抛出这个异常的罪魁祸首。需要谨记的是这个异常的一个副作用是NoClassDefFoundError,而Java程序抛出这个异常的位置可能会离java.lang.ExceptionInInitializerError很远,这取决于你的客户端代码何时引用到这个类。因此,在查看类路径解决NoClassDefFoundError异常之前,最好先看看日志有没有出现ExceptionInInitializerError。

### Spark 编译时遇到 `java.lang.BootstrapMethodError` 异常解决方案 `java.lang.BootstrapMethodError` 是 Java 8 及以上版本中引入的一种错误,通常与 **invokedynamic** 指令相关,常见于使用了 Lambda 表达式或某些高级语言特性的情况下。在 Spark 编译或运行过程中,如果出现此类错误,可能的原因包括: - **Java 版本不兼容**:Spark 通常要求使用特定版本的 Java(如 Java 8 或 Java 11),不同版本之间可能存在字节码兼容性问题。 - **依赖版本冲突**:某些依赖库可能使用了与当前 Java 版本不兼容的 Lambda 表达式或字节码增强技术。 - **编译器或运行时类路径错误**:类路径中可能存在多个版本的库文件,导致调用点(Call Site)初始化失败。 #### 解决方案 1. **检查 Java 版本** 确保使用的 Java 版本与 Spark 版本兼容。例如,Spark 3.x 推荐使用 Java 11,而 Spark 2.x 通常推荐使用 Java 8。可以通过以下命令查看当前 Java 版本: ```bash java -version ``` 如果版本不匹配,建议切换 Java 版本,使用 `update-alternatives`(Linux)或 `jenv`(macOS)进行管理。 2. **清理 Maven 或 SBT 缓存** 如果使用 Maven 或 SBT 进行编译,可以尝试清除本地缓存并重新下载依赖: ```bash sbt clean compile sbt clean package ``` 或者对于 Maven: ```bash mvn clean install ``` 3. **检查依赖版本一致性** 确保项目中所有依赖库的版本与 Spark 兼容。例如,某些 Hadoop、Hive 或第三方库可能使用了与当前 Java 版本不兼容的特性。可以尝试升级或降级相关依赖版本。 4. **启用 JVM 参数以调试 invokedynamic 错误** 在编译或运行时添加以下 JVM 参数,可以获取更详细的错误信息: ```bash -XX:+UnlockDiagnosticVMOptions -XX:+VerboseVerification ``` 这有助于识别具体的调用点失败原因。 5. **使用兼容的编译器插件** 如果使用的是 Scala,确保使用的 Scala 编译器版本与 Spark 兼容。例如,Spark 3.x 通常使用 Scala 2.12 或 2.13,而 Spark 2.x 使用 Scala 2.11。可以在 `build.sbt` 中指定: ```scala scalaVersion := "2.12.15" ``` 6. **避免使用 Java 17 或更高版本中的新特性** 如果使用的是 Java 17 或更高版本,某些旧版本的 Spark 或依赖库可能无法正确处理新的字节码结构。可以尝试降级到 Java 11 或使用兼容性补丁。 #### 示例:修改 `build.sbt` 中的 Java 版本设置 ```scala javacOptions ++= Seq("-source", "11", "-target", "11") ``` #### 示例:使用 `jenv` 切换 Java 版本 ```bash jenv global 11 ``` #### 示例:检查依赖冲突 ```bash sbt dependencyTree ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值