在JAVA開發中,main線程中拋出java.lang.NoClassDefFoundError是一個非常普遍且比較難解決的問題。解決這個問題的復雜性主要取決於你的軟件大小和中間件部署情況,尤其要考慮在應用中出現的數量眾多的classloader的情況。
本文將從一個比較高的角度看這個問題,主要是介紹java classloader機制。
那么,什么是java.lang.NoClassDefFoundError呢?
我們先簡單的看一下這個問題,這個runtime異常是JVM拋出的,當JVM發現一個classloader試圖去Load一個class,而此class在當前的classloader tree中找不到的時候,就會拋出此異常。
很明顯,這個問題是運行期的問題,在編譯期一切正常。
那么,解決起來很簡單,就是把jar包放到classpath下不就行了么?
ok,到這里還不行,這個問題解決起來不是那么容易的,在運行期的程序classpath中加入缺少的jar包僅僅是一種解決方法。關鍵是,我們必須掌握此種異常的根本原因,以后解決此問題就可以以不變應萬變。
現在,先記住,此問題不一定是由於在classpath中缺少class的定義。
java classloader概述
在深入分析之前,我們必須掌握java classloader的基本原理。class loader是一個java對象,它負責load所有的class,負責查找、加載、生成一個class的基本定義信息。classloader自身采用了委托代理機制來查詢class,每一個classloader的實例都有一個父classloader,所以,當一個應用的classloader去加載class A的時候,首先發生的事情是classloader委托其父classloader去加載class A,經過一串鏈式查找后,最終任務會落在JVM的系統啟動classloader上。
那哪里會出問題?當你期望你的應用classloader能加載class A,但是當class A被其任意一個父classloader查詢到並加載,那么就可能會出現java.lang.NoClassDefFoundError。當所有的父classloader都找不到class A的時候,才會由應用自己的classloader嘗試加載。
本文包含了NoClassDefFoundError的原因分析和例子程序,並且給出了建議的處理策略。
NoClassDefFoundError 問題原因1:缺少jar包
首先最常見的原因是classpath的配置問題。例子程序:
本例子程序嘗試創建一個新的CallerClassA實例,然后執行他的一個方法,此方法引用了類ReferencingClassA,本例子演示了classpath問題導致的NoClassDefFoundError ,本例子還打印了當前的classloader chain的情況,以便進一步的分析。這個打印信息對你以后分析此類問題也很有幫助的:
程序
- public class NoClassDefFoundErrorSimulator {
- public static void main(String[] args) {
- System.out
- .println("java.lang.NoClassDefFoundError Simulator");
- // Print current Classloader context
- System.out.println("\nCurrent ClassLoader chain: "
- + ClassloaderUtil.getCurrentClassloaderDetail());
- // 1. Create a new instance of CallerClassA
- CallerClassA caller = new CallerClassA();
- // 2. Execute method of the caller
- caller.doSomething();
- System.out.println("done!");
- }
- }
- public class CallerClassA {
- private final static String CLAZZ = CallerClassA.class.getName();
- static {
- System.out.println("Classloading of " + CLAZZ + " in progress..."
- + ClassloaderUtil.getCurrentClassloaderDetail());
- }
- public CallerClassA() {
- System.out.println("Creating a new instance of "
- + CallerClassA.class.getName() + "...");
- }
- public void doSomething() {
- // Create a new instance of ReferencingClassA
- ReferencingClassA referencingClass = new ReferencingClassA();
- }
- }
- public class ReferencingClassA {
- private final static String CLAZZ = ReferencingClassA.class.getName();
- static {
- System.out.println("Classloading of " + CLAZZ + " in progress..."
- + ClassloaderUtil.getCurrentClassloaderDetail());
- }
- public ReferencingClassA() {
- System.out.println("Creating a new instance of "
- + ReferencingClassA.class.getName() + "...");
- Maps.newHashMap();
- }
- public void doSomething() {
- // nothing to do...
- }
- }
打印classloader工具類:
- public class ClassloaderUtil {
- public static String getCurrentClassloaderDetail() {
- StringBuffer classLoaderDetail = new StringBuffer();
- Stack<ClassLoader> classLoaderStack = new Stack<ClassLoader>();
- ClassLoader currentClassLoader = Thread.currentThread()
- .getContextClassLoader();
- classLoaderDetail
- .append("\n-----------------------------------------------------------------\n");
- // Build a Stack of the current ClassLoader chain
- while (currentClassLoader != null) {
- classLoaderStack.push(currentClassLoader);
- currentClassLoader = currentClassLoader.getParent();
- }
- // Print ClassLoader parent chain
- while (classLoaderStack.size() > 0) {
- ClassLoader classLoader = classLoaderStack.pop();
- // Print current
- classLoaderDetail.append(classLoader);
- if (classLoaderStack.size() > 0) {
- classLoaderDetail.append("\n--- delegation ---\n");
- } else {
- classLoaderDetail.append(" **Current ClassLoader**");
- }
- }
- classLoaderDetail
- .append("\n-----------------------------------------------------------------\n");
- return classLoaderDetail.toString();
- }
- }
正常運行:
- java -classpath .;../guava-12.0.jar NoClassDefFoundError.NoClassDefFoundErrorSimulator
- java.lang.NoClassDefFoundError Simulator
- Current ClassLoader chain:
- -----------------------------------------------------------------
- sun.misc.Launcher$ExtClassLoader@addbf1
- --- delegation ---
- sun.misc.Launcher$AppClassLoader@19821f **Current ClassLoader**
- -----------------------------------------------------------------
- Classloading of NoClassDefFoundError.CallerClassA in progress...
- -----------------------------------------------------------------
- sun.misc.Launcher$ExtClassLoader@addbf1
- --- delegation ---
- sun.misc.Launcher$AppClassLoader@19821f **Current ClassLoader**
- -----------------------------------------------------------------
- Creating a new instance of NoClassDefFoundError.CallerClassA...
- Classloading of NoClassDefFoundError.ReferencingClassA in progress...
- -----------------------------------------------------------------
- sun.misc.Launcher$ExtClassLoader@addbf1
- --- delegation ---
- sun.misc.Launcher$AppClassLoader@19821f **Current ClassLoader**
- -----------------------------------------------------------------
- Creating a new instance of NoClassDefFoundError.ReferencingClassA...
- done!
異常重現:
- java -classpath . NoClassDefFoundError.NoCl
- java.lang.NoClassDefFoundError Simulator
- Current ClassLoader chain:
- -----------------------------------------------------------------
- sun.misc.Launcher$ExtClassLoader@addbf1
- --- delegation ---
- sun.misc.Launcher$AppClassLoader@19821f **Current ClassLoader**
- -----------------------------------------------------------------
- Classloading of NoClassDefFoundError.CallerClassA in progress...
- -----------------------------------------------------------------
- sun.misc.Launcher$ExtClassLoader@addbf1
- --- delegation ---
- sun.misc.Launcher$AppClassLoader@19821f **Current ClassLoader**
- -----------------------------------------------------------------
- Creating a new instance of NoClassDefFoundError.CallerClassA...
- Classloading of NoClassDefFoundError.ReferencingClassA in progress...
- -----------------------------------------------------------------
- sun.misc.Launcher$ExtClassLoader@addbf1
- --- delegation ---
- sun.misc.Launcher$AppClassLoader@19821f **Current ClassLoader**
- -----------------------------------------------------------------
- Creating a new instance of NoClassDefFoundError.ReferencingClassA...
- Exception in thread "main" java.lang.NoClassDefFoundError: com/google/common/collect/Maps
- at NoClassDefFoundError.ReferencingClassA.<init>(ReferencingClassA.java:28)
- at NoClassDefFoundError.CallerClassA.doSomething(CallerClassA.java:31)
- at NoClassDefFoundError.NoClassDefFoundErrorSimulator.main(NoClassDefFoundErrorSimulator.jav
- Caused by: java.lang.ClassNotFoundException: com.google.common.collect.Maps
- at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
- at java.security.AccessController.doPrivileged(Native Method)
- at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
- at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
- at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
- at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
- ... 3 more
發生了什么?當你在classpath中不包含guava的引用的時候,由於ReferencingClassA在運行期引用了此類,導致了classloader報告找不到此類,從而出現NoClassDefFoundError。
classloader分析
注意:
- Classloading of NoClassDefFoundError.CallerClassA in progress...
- -----------------------------------------------------------------
- sun.misc.Launcher$ExtClassLoader@addbf1
- --- delegation ---
- sun.misc.Launcher$AppClassLoader@19821f **Current ClassLoader**
- -------------------------
sun.misc.Launcher$AppClassLoader是系統的classloader,負責根據classpath設置在啟動的時候加載應用需要的class。
sun.misc.Launcher$ExtClassLoader是擴展classloader,負責從java_home/lib/etc以及其他使用java.ext.dirs配置的目錄從加載擴展java class。
從打印結果可以看出,sun.misc.Launcher$ExtClassLoader是系統classloader的實際父類。
建議處理策略
分析異常堆棧,找到缺少的java類名稱,在classpath中驗證,確保編譯和運行期都能找到此類。