Java学习之NoClassDefFoundError、ClassNotFoundException、NoSuchMethodError

概述

在Java开发者的职业编码生涯中,会无数次遇到这几个异常报错。Java异常体系大致提一些,不是本文的重点。JVM类加载机制是理解这三个异常的核心。

理论

Checked Exception vs Unchecked Exception

从两个异常的定义看,NoClassDefFoundError是一种Unchecked Exception(也称 runtime Exception),而ClassNotFoundException 是Checked Exception。

NoClassDefFoundError

Java 规范(源码)

/**
* Thrown if the Java Virtual Machine or a ClassLoader instance tries to load in the definition of a class (as part of a normal method call or as part of creating a new instance using the new expression) and no definition of the class could be found.
* The searched-for class definition existed when the currently executing class was compiled, but the  definition can no longer be found.
*/
public class NoClassDefFoundError extends LinkageError {
}

类加载器试图加载类的定义,但是找不到这个类的定义,而实际上这个类文件是存在的。

发生NoClassDefFoundError错误,是因为JVM在编译时能找到合适的类,而在运行时不能找到合适的类,导致的错误。例如在运行时想调用某个类的方法或者访问这个类的静态成员的时候,发现这个类不可用,此时JVM就会抛出NoClassDefFoundError错误。
NoClassDefFoundError发生在编译时对应的类可用,而运行时在Java的classpath路径中,对应的类不可用导致的错误。发生NoClassDefFoundError错误时,你能看到如下的错误日志:Exception in thread “main” java.lang.NoClassDefFoundError。错误的信息很明显地指明main线程无法找到指定的类,而这个main线程可能时主线程或者其他子线程。如果是主线程发生错误,程序将崩溃或停止,而如果是子线程,则子线程停止,其他线程继续运行。

解决思路

但是,很多人在碰到NoClassDefFoundError 异常时会下意识的去检查是不是缺少.class 文件。而实际上,这个异常的来源根本不是因为缺少 .class 文件。而碰到这个异常的解决办法,一般需要检查这个类定义中的初始化部分(如类属性定义、static 块等)的代码是否有抛异常的可能,如果是 static 块,可以考虑在其中将异常捕获并打印堆栈等,或者直接在对类进行初始化调用(如 new Foobar())时作 try catch。

有时候会出现Exception in thread “main” java.lang.NoClassDefFoundError: com/sun/tools/javac/Main 这样的错误,这个错误说明你的Classpath, PATH 或者 JAVA_HOME没有安装配置正确或者JDK的安装不正确。解决办法:重装JDK。

NoClassDefFoundError的错误是因为在运行时类加载器在classpath下找不到需要加载的类,所以需要把对应的类加载到classpath中,或者检查为什么类在classpath中是不可用的,这个发生可能的原因如下:

  • 对应的Class在java的classpath中不可用
  • 你可能用jar命令运行你的程序,但类并没有在jar文件的manifest文件中的classpath属性中定义
  • 可能程序的启动脚本覆盖了原来的classpath环境变量
  • 因为NoClassDefFoundError是LinkageError的一个子类,所以可能由于程序依赖的原生的类库不可用而导致
  • 检查日志文件中是否有java.lang.ExceptionInInitializerError这样的错误,NoClassDefFoundError有可能是由于静态初始化失败导致的
  • 如果你工作在J2EE的环境,有多个不同的类加载器,也可能导致NoClassDefFoundError

由于NoClassDefFoundError是LinkageError的子类,而LinkageError的错误在依赖其他的类时会发生,所以如果你的程序依赖原生的类库和需要的dll不存在时,有可能出现java.lang.NoClassDefFoundError。这种错误也可能抛出java.lang.UnsatisfiedLinkError: no dll in java.library.path Exception Java这样的异常。解决的办法是把依赖的类库和dll跟你的jar包放在一起。
Jar文件的权限问题也可能导致NoClassDefFoundError,如果你的程序运行在像Linux这样多用户的操作系统种,你需要把你应用相关的资源文件,如Jar文件,类库文件,配置文件的权限单独分配给程序所属用户组,如果你使用多个用户不同程序共享的jar包时,很容易出现权限问题。比如其他用户应用所属权限的jar包你的程序没有权限访问,会导致java.lang.NoClassDefFoundError的错误。
基于XML配置的程序也可能导致NoClassDefFoundError的错误。比如大多数Java的框架像Spring,Struts使用xml配置获取对应的bean信息,如果你输入错误的名称,程序可能会加载其他错误的类而导致NoClassDefFoundError异常。在使用Spring MVC框架,在部署War文件或者EAR文件时就经常会出现Exception in thread “main” java.lang.NoClassDefFoundError。
在有多个ClassLoader的J2EE的环境中,很容易出现NoClassDefFoundError的错误。由于J2EE没有指明标准的类加载器,使用的类加载器依赖与不同的容器像Tomcat、WebLogic,WebSphere加载J2EE的不同组件如War包或者EJB-JAR包。类加载器的工作原理。

示例

NoClassDefFoundError解决示例
当发生由于缺少jar文件,或者jar文件没有添加到classpath,或者jar的文件名发生变更会导致java.lang.NoClassDefFoundError的错误。
当类不在classpath中时,这种情况很难确切的知道,但如果在程序中打印出System.getproperty(“java.classpath”),可以得到程序实际运行的classpath
运行时明确指定你认为程序能正常运行的 -classpath 参数,如果增加之后程序能正常运行,说明原来程序的classpath被其他人覆盖了。
NoClassDefFoundError也可能由于类的静态初始化模块错误导致,当你的类执行一些静态初始化模块操作,如果初始化模块抛出异常,哪些依赖这个类的其他类会抛出NoClassDefFoundError的错误。如果你查看程序日志,会发现一些java.lang.ExceptionInInitializerError的错误日志,ExceptionInInitializerError的错误会导致java.lang.NoClassDefFoundError: Could not initialize class,如下面的代码示例:

/**
 * Java program to demonstrate how failure of static initialization subsequently cause
 * java.lang.NoClassDefFoundError in Java.
 * @author Javin Paul
 */
public class Demo {
    public static void main(String args[]) {
        List<User> users = new ArrayList<>(2);
        for (int i = 0; i < 2; i++) {
            try {
                //will throw NoClassDefFoundError
                users.add(new User(String.valueOf(i)));
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }
    }

}


class User {
    private static String USER_ID = getUserId();

    public User(String id) {
        USER_ID = id;
    }

    private static String getUserId() {
        throw new RuntimeException("UserId Not found");
    }
}

Output:

java.lang.ExceptionInInitializerError
    at testing.NoClassDefFoundErrorDueToStaticInitFailure.main(NoClassDefFoundErrorDueToStaticInitFailure.java:23)
Caused by: java.lang.RuntimeException: UserId Not found
    at testing.User.getUserId(NoClassDefFoundErrorDueToStaticInitFailure.java:41)
    at testing.User.(NoClassDefFoundErrorDueToStaticInitFailure.java:35)
    ... 1 more
java.lang.NoClassDefFoundError: Could not initialize class testing.User
    at testing.NoClassDefFoundErrorDueToStaticInitFailure.main(NoClassDefFoundErrorDueToStaticInitFailure.java:23)

NoSuchMethodError

和NoClassDefFoundError非常类似,一个是找不到类,一个是找不到方法。排查思路都是从classpath(或者maven的dependency)入手。

ClassNotFoundException

Java 规范(源码):

/**
 * Thrown when an application tries to load in a class through its string name using:
 * 1. The forName method in class Class.
 * 2. The findSystemClass method in class ClassLoader .
 * 3. The loadClass method in class ClassLoader.
 * but no definition for the class with the specified name could be found.
 * 

As of release 1.4, this exception has been retrofitted to conform to * the general purpose exception-chaining mechanism. The "optional exception * that was raised while loading the class" that may be provided at * construction time and accessed via the {@link #getException()} method is * now known as the cause, and may be accessed via the {@link * Throwable#getCause()} method, as well as the aforementioned "legacy method." public class ClassNotFoundException extends ReflectiveOperationException { }

找不到指定的class。常见的场景就是(注释翻译而来):

  1. 调用class的forName方法时,找不到指定的类
  2. ClassLoader 中的 findSystemClass() 方法时,找不到指定的类
  3. ClassLoader 中的 loadClass() 方法时,找不到指定的类

ClassNotFoundException 异常抛出的根本原因是类文件找不到,缺少.class 文件,比如缺少引某个 jar,解决方法通常需要检查一下 classpath 下能不能找到包含缺失 .class 文件的 jar。

解决思路

检查classpath,或者pom文件里面的dependency的依赖冲突;

区别

NoClassDefFoundError发生在JVM在动态运行时,根据类名,在classpath中找到对应的类进行加载,但当它找不到这个类时,就发生NoClassDefFoundError的错误;而ClassNotFoundException是编译时,在classpath中找不到对应的类而发生的错误。ClassNotFoundException比NoClassDefFoundError容易解决,是因为在编译时就知道错误发生,并且完全是由于环境的问题导致。而如果你在J2EE的环境下工作,并且得到NoClassDefFoundError的异常,而且对应的错误的类是确实存在的,这说明这个类对于类加载器来说,可能是不可见的。

NoClassDefFoundErorr是一个链接错误。java有8个链接错误:
ClassCircularityError, ClassFormatError, ExceptionInInitializerError,
IncompatibleClassChangeError, NoClassDefFoundError,UnsatisfiedLinkError, VerifyError
链接错误的意思是链接过程出错。链接出错报的是代码的问题,因为java是在类的层次上实现的动态链接,所以没有运行时的class对象就没有办法链接。因此这个错可以理解成没有链接目标。一个没有链接目标的类可能导致很多的问题,所以必须报错,并且它不会正确被安装,也就是说这个类将不可用,并由此引发更多的问题。所以它是一个系统error。

ClassNotfoundException报的是一个找不到的类而不是一个有问题的类。比如A.B,如果B不存在,那么:B是找不到类错;A是链接错。

链接出错(NoClassDefFoundErorr)意味着有代码(链接发出者的),而找不到类定义(ClassNotfoundException)则意味着不管在内存还是外部都找不到被链接的类。
链接错误是字节码的问题。是还未(成功)装入的代码。它可能包含找不到类的错,但是它报告的不是这个错,而是代码本身的问题。
而找不到类,报的是查找类文件这个行动的失败。或者说没有这个类文件在外部(磁盘,网络或任何其它字节码来源)不存在或找不到。
两个不同的问题:一是代码错;而是代码没有。
你可以试着装入一段代码,那么第一个问题是这个代码可能没有,第二个则是它可能引用了不存在的代码。这是两个问题。

总结

总结来说,类加载器基于三个机制:委托、可见性和单一性

  • 委托机制是指将加载一个类的请求交给父类加载器,如果这个父类加载器不能够找到或者加载这个类,那么再加载它;
  • 可见性的原理是子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类;
  • 单一性原理是指一个类仅加载一次,这是由委托机制确保子类加载器不会再次加载父类加载器加载过的类。

现在假设一个User类在WAR文件和EJB-JAR文件都存在,并且被WAR ClassLoader加载,而WAR ClassLoader是加载EJB-JAR ClassLoader的子ClassLoader。当EJB-JAR中代码引用这个User类时,加载EJB-JAR所有class的Classloader找不到这个类,因为这个类已经被EJB-JAR classloader的子加载器WAR classloader加载。

这会导致的结果就是对User类出现NoClassDefFoundError异常,而如果在两个JAR包中这个User类都存在,如果你使用equals方法比较两个类的对象时,会出现ClassCastException的异常,因为两个不同类加载器加载的类无法进行比较。

扩展阅读

  • 3 ways to solve java.lang.NoClassDefFoundError in Java J2EE
  • When a class is loaded and initialized in JVM
  • java.lang.ClassNotFoundException与java.lang.NoClassDefFoundError的区别
  • ClassNotfoundException与NoClassDeFoundeEror

你可能感兴趣的:(Java,java)