众所周知,Java的类加载机制采用了双亲委派模型,导致在进行类加载的时候会有多个加载器,这种复杂的机制,有时候会导致‘Exception in thread main java.lang.NoClassDefFoundError’这个异常,虽然可能你认为相应的类和jar包就在某个类加载器中。下面的文字,会试图尝试解释为什么会发生这种情况。
 
下面提供了一个简单的java程序来帮助理解问题的发生。
 
 默认的JVM的类加载委派模型
 
默认的类加载委派模式是从下向上的,也就是双亲委派。这意味着JVM会从下往上来委托类加载器去查找和加载用户的类,如果父加载器没能加载相应的类,这时才会尝试在当前线程上下文的类加载器中去加载,一般而言是子加载器。

NoClassDefFoundError这种异常时有发生,比如,用户自己的jar包打的不对,再比如,依赖的第三方jar包或者容器注入的类型,这些情况都有可能导致问题的发生。
 
在以上这些场景中:
  • JVM将程序的部分类型在父加载器中加载了(比如系统或者父加载器)
  • JVM将程序的其他部分想要在子加载器中加载(比如容器或者用户自定义加载器)
当在父加载器中加载的类尝试去通过子加载器加载相应的类时会发生什么?当然是NoClassDefFoundError!
因为父加载器对子加载器一无所知。只有当引用的类在是父加载器或当前线程上下文加载器中,才可能会去加载,否则就会抛出java.lang.NoClassDefFoundError。
 
下面的代码将会展示这些问题:
 
样例Java程序
 
为了演示,程序被如下分割:
  • 主程序NoClassDefFoundErrorSimulator会被打包在MainProgram.jar
  • 日志输出程序JavaEETrainingUtil也被打包在MainProgram.jar
  • caller类CallerClassA被打包在caller.jar
  • 被引用的类ReferencingClassA被打包在referencer.jar
然后执行下面的任务:
  • 创建一个子加载器(java.net.URLClassLoader)
  • 将caller.jar和referencher.jar分配给上面创建的子加载器
  • 将当前线程上下文加载器更改为上面的子加载器
  • 尝试从当前线程上下文加载器加载和创建CallerClassA类的实例
  • 日志输出用来帮助理解类加载器的变化和线程上下文加载器的状态
下面将展示错误的打包方式和默认的类加载委派模型是如何产生NoClassDefFoundError这个异常的。
 
** JavaEETrainingUtil source code can be found from the article part #2
  1. #### NoClassDefFoundErrorSimulator.java
  2. package org.ph.javaee.training3;
  3.  
  4. import java.net.URL;
  5. import java.net.URLClassLoader;
  6.  
  7. import org.ph.javaee.training.util.JavaEETrainingUtil;
  8.  
  9. /**
  10. * NoClassDefFoundErrorSimulator
  11. * @author Pierre-Hugues Charbonneau
  12. *
  13. */
  14. public class NoClassDefFoundErrorSimulator {
  15.  
  16. /**
  17. * @param args
  18. */
  19. public static void main(String[] args) {
  20.  
  21. System.out.println("java.lang.NoClassDefFoundError Simulator - Training 3");
  22. System.out.println("Author: Pierre-Hugues Charbonneau");
  23. System.out.println("http://javaeesupportpatterns.blogspot.com");
  24.  
  25. // Local variables
  26. String currentThreadName = Thread.currentThread().getName();
  27. String callerFullClassName = "org.ph.javaee.training3.CallerClassA";
  28.  
  29. // Print current ClassLoader context & Thread
  30. System.out.println("\nCurrent Thread name: '"+currentThreadName+"'");
  31. System.out.println("Initial ClassLoader chain: "+JavaEETrainingUtil.getCurrentClassloaderDetail());
  32.  
  33. try {
  34. // Location of the application code for our child ClassLoader
  35. URL[] webAppLibURL = new URL[] {new URL("file:caller.jar"),new URL("file:referencer.jar")};
  36.  
  37. // Child ClassLoader instance creation
  38. URLClassLoader childClassLoader = new URLClassLoader(webAppLibURL);
  39.  
  40. /*** Application code execution... ***/
  41.  
  42. // 1. Change the current Thread ClassLoader to the child ClassLoader
  43. Thread.currentThread().setContextClassLoader(childClassLoader);
  44. System.out.println(">> Thread '"+currentThreadName+"' Context ClassLoader now changed to '"+childClassLoader+"'");
  45. System.out.println("\nNew ClassLoader chain: "+JavaEETrainingUtil.getCurrentClassloaderDetail());
  46.  
  47. // 2. Load the caller Class within the child ClassLoader...
  48. System.out.println(">> Loading '"+callerFullClassName+"' to child ClassLoader '"+childClassLoader+"'...");
  49. Class<?> callerClass = childClassLoader.loadClass(callerFullClassName);
  50.  
  51. // 3. Create a new instance of CallerClassA
  52. Object callerClassInstance = callerClass.newInstance();
  53.  
  54. } catch (Throwable any) {
  55. System.out.println("Throwable: "+any);
  56. any.printStackTrace();
  57. }
  58.  
  59. System.out.println("\nSimulator completed!");
  60. }
  61. }
  1. #### CallerClassA.java
  2. package org.ph.javaee.training3;
  3.  
  4. import org.ph.javaee.training3.ReferencingClassA;
  5.  
  6. /**
  7. * CallerClassA
  8. * @author Pierre-Hugues Charbonneau
  9. *
  10. */
  11. public class CallerClassA {
  12.  
  13. private final static Class<CallerClassA> CLAZZ = CallerClassA.class;
  14.  
  15. static {
  16. System.out.println("Class loading of "+CLAZZ+" from ClassLoader '"+CLAZZ.getClassLoader()+"' in progress...");
  17. }
  18.  
  19. public CallerClassA() {
  20. System.out.println("Creating a new instance of "+CallerClassA.class.getName()+"...");
  21.  
  22. doSomething();
  23. }
  24.  
  25. private void doSomething() {
  26.  
  27. // Create a new instance of ReferencingClassA
  28. ReferencingClassA referencingClass = new ReferencingClassA();
  29. }
  30. }
  1. #### ReferencingClassA.java
  2. package org.ph.javaee.training3;
  3.  
  4. /**
  5. * ReferencingClassA
  6. * @author Pierre-Hugues Charbonneau
  7. *
  8. */
  9. public class ReferencingClassA {
  10.  
  11. private final static Class<ReferencingClassA> CLAZZ = ReferencingClassA.class;
  12.  
  13. static {
  14. System.out.println("Class loading of "+CLAZZ+" from ClassLoader '"+CLAZZ.getClassLoader()+"' in progress...");
  15. }
  16.  
  17. public ReferencingClassA() {
  18. System.out.println("Creating a new instance of "+ReferencingClassA.class.getName()+"...");
  19. }
  20.  
  21. public void doSomething() {
  22. //nothing to do...
  23. }
  24. }
问题重现
 
为了复现问题,简单的将caller和referencing分别打包并且赋给不同的加载器。现在,可以先以正确的jar包部署来执行程序: 
  • 主程序和工具包被部署在父加载器 (SYSTEM classpath)
  • CallerClassA 和ReferencingClassA被部署在子加载器中
  1. ## Baseline (normal execution)
  2. <JDK_HOME>\bin>java -classpath MainProgram.jar org.ph.javaee.training3.NoClassDefFoundErrorSimulator
  3.  
  4. java.lang.NoClassDefFoundError Simulator - Training 3
  5. Author: Pierre-Hugues Charbonneau
  6. http://javaeesupportpatterns.blogspot.com
  7.  
  8. Current Thread name: 'main'
  9. Initial ClassLoader chain:
  10. -----------------------------------------------------------------
  11. sun.misc.Launcher$ExtClassLoader@17c1e333
  12. --- delegation ---
  13. sun.misc.Launcher$AppClassLoader@214c4ac9 **Current Thread 'main' Context ClassLoader**
  14. -----------------------------------------------------------------
  15.  
  16. >> Thread 'main' Context ClassLoader now changed to 'java.net.URLClassLoader@6a4d37e5'
  17.  
  18. New ClassLoader chain:
  19. -----------------------------------------------------------------
  20. sun.misc.Launcher$ExtClassLoader@17c1e333
  21. --- delegation ---
  22. sun.misc.Launcher$AppClassLoader@214c4ac9
  23. --- delegation ---
  24. java.net.URLClassLoader@6a4d37e5 **Current Thread 'main' Context ClassLoader**
  25. -----------------------------------------------------------------
  26.  
  27. >> Loading 'org.ph.javaee.training3.CallerClassA' to child ClassLoader 'java.net.URLClassLoader@6a4d37e5'...
  28. Class loading of class org.ph.javaee.training3.CallerClassA from ClassLoader 'java.net.URLClassLoader@6a4d37e5' in progress...
  29. Creating a new instance of org.ph.javaee.training3.CallerClassA...
  30. Class loading of class org.ph.javaee.training3.ReferencingClassA from ClassLoader 'java.net.URLClassLoader@6a4d37e5' in progress...
  31. Creating a new instance of org.ph.javaee.training3.ReferencingClassA...
  32.  
  33. Simulator completed!

在基准测试中,主程序可以正常运行,CallerClassA和他引用的类都可以被加载和实例化。
 
现在再次尝试运行程序,但是以一种错误的打包和部署方式来进行:
  • 主程序和工具类都被部署在父加载器(SYSTEM classpath)
  • CallerClassA和ReferencingClassA被部署在子加载器
  • CallerClassA (caller.jar)也被部署在父加载器
  1. ## Problem reproduction run (static variable initializer failure)
  2. <JDK_HOME>\bin>java -classpath MainProgram.jar;caller.jar org.ph.javaee.training3.NoClassDefFoundErrorSimulator
  3.  
  4. java.lang.NoClassDefFoundError Simulator - Training 3
  5. Author: Pierre-Hugues Charbonneau
  6. http://javaeesupportpatterns.blogspot.com
  7.  
  8. Current Thread name: 'main'
  9. Initial ClassLoader chain:
  10. -----------------------------------------------------------------
  11. sun.misc.Launcher$ExtClassLoader@17c1e333
  12. --- delegation ---
  13. sun.misc.Launcher$AppClassLoader@214c4ac9 **Current Thread 'main' Context ClassLoader**
  14. -----------------------------------------------------------------
  15.  
  16. >> Thread 'main' Context ClassLoader now changed to 'java.net.URLClassLoader@6a4d37e5'
  17.  
  18. New ClassLoader chain:
  19. -----------------------------------------------------------------
  20. sun.misc.Launcher$ExtClassLoader@17c1e333
  21. --- delegation ---
  22. sun.misc.Launcher$AppClassLoader@214c4ac9
  23. --- delegation ---
  24. java.net.URLClassLoader@6a4d37e5 **Current Thread 'main' Context ClassLoader**
  25. -----------------------------------------------------------------
  26.  
  27. >> Loading 'org.ph.javaee.training3.CallerClassA' to child ClassLoader 'java.net.URLClassLoader@6a4d37e5'...
  28. Class loading of class org.ph.javaee.training3.CallerClassA from ClassLoader 'sun.misc.Launcher$AppClassLoader@214c4ac9' in progress...// Caller is loaded from the parent class loader, why???
  29. Creating a new instance of org.ph.javaee.training3.CallerClassA...
  30. Throwable: java.lang.NoClassDefFoundError: org/ph/javaee/training3/ReferencingClassA
  31. java.lang.NoClassDefFoundError: org/ph/javaee/training3/ReferencingClassA
  32. at org.ph.javaee.training3.CallerClassA.doSomething(CallerClassA.java:27)
  33. at org.ph.javaee.training3.CallerClassA.<init>(CallerClassA.java:21)
  34. at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
  35. at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
  36. at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
  37. at java.lang.reflect.Constructor.newInstance(Unknown Source)
  38. at java.lang.Class.newInstance0(Unknown Source)
  39. at java.lang.Class.newInstance(Unknown Source)
  40. at org.ph.javaee.training3.NoClassDefFoundErrorSimulator.main(NoClassDefFoundErrorSimulator.java:51)
  41. Caused by: java.lang.ClassNotFoundException: org.ph.javaee.training3.ReferencingClassA
  42. at java.net.URLClassLoader$1.run(Unknown Source)
  43. at java.net.URLClassLoader$1.run(Unknown Source)
  44. at java.security.AccessController.doPrivileged(Native Method)
  45. at java.net.URLClassLoader.findClass(Unknown Source)
  46. at java.lang.ClassLoader.loadClass(Unknown Source)
  47. at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
  48. at java.lang.ClassLoader.loadClass(Unknown Source)
  49. ... 9 more
  50.  
  51. Simulator completed!
发生什么了?
  • 主程序和工具类都正常的从父加载器加载成功 (sun.misc.Launcher$AppClassLoader)
  • 线程上下文加载器被更改为包含caller和reference的子加载器。
  • 但是我们会看到CallerClassA 会被父加载器加载 (sun.misc.Launcher$AppClassLoader) ,并没有被子加载器加载
  • 由于ReferencingClassA 没有被部署在父加载器, 所以他不能在当前加载器中加载,又由于父加载器对子加载器的无知,所以也不可能被子加载器加载。然后就报错NoClassDefFoundError。
 

关键点在于理解为什么CallerClassA会被父加载器加载。答案就在于默认的类加载委派模型,尽管子加载器和父加载器都包含caller的jar包,但默认的委派模型是父优先,这就导致caller只会在父加载器中被加载。但同时caller引用的ReferencingClassA却被部署在了子加载器,所以java.lang.NoClassDefFoundError自然会发生。
 正如你所见,由于默认的类加载委托模型的存在,代码的打包或者第三方的api都会导致这个问题的发生。所以,很重要的事情就是,你需要检视你的类加载器链条,确定是否有相同的代码或类库被重复部署在不同的父加载器和子加载器中。
 
建议和策略
 
如下是给出的建议和策略:
  • 仔细检视异常java.lang.NoClassDefFoundError并确定到底是哪个类导致问题
  • 检视受影响的程序的打包方式,看看是否能找到是相应的类存在重复或者错误的部署方式。(SYSTEM class path, EAR file, Java EE container itself etc.).
  • 如果找到了,那就需要将响应的类库从受影响的类加载器中移除。(有时会很复杂)
  • 启用jvm的属性verbose,比如–verbose:class很有帮助,会帮助你定位类是从哪个加载器中加载的。

翻译自:https://javaeesupportpatterns.blogspot.com/2012/08/javalangnoclassdeffounderror-parent.html

Thread.currentThread().setContextClassLoader为什么不生效与java.lang.NoClassDefFoundError之Java类加载的Parent first Classloader的更多相关文章

  1. tomcat启动报错 关键字:java.lang.NoClassDefFoundError和 java.lang.ClassNotFoundExceeption

    启动tomcat时报错情况如下图所示:实际上就是依赖的bean出错,百度上很多方法都说是tomcat没有部署正确,项目-->右键----->proterties----->Targe ...

  2. CentOS6.5 静默安装Oracle 11g过程中提示:Exception in thread “main” java.lang.NoClassDefFoundError

    原来是系统中设置了DISPLAY环境变量,执行: [oracle@qa26 database]$ ./runInstaller  -silent -responseFile /usr/local/or ...

  3. 【转】怎么解决java.lang.NoClassDefFoundError错误 ,以及类的加载机制

    转自http://blog.csdn.net/jamesjxin/article/details/46606307 前言 在日常Java开发中,我们经常碰到java.lang.NoClassDefFo ...

  4. 【eclipse】 怎么解决java.lang.NoClassDefFoundError错误

    前言 在日常Java开 发中,我们经常碰到java.lang.NoClassDefFoundError这样的错误,需要花费很多时间去找错误的原因,具体是哪个类不见了?类 明明还在,为什么找不到?而且我 ...

  5. 在centOS上安装oracle出现java.lang.NoClassDefFoundError问题及解决方法

    问题一:CentOS6.5 静默安装Oracle 11G过程中提示:Exception in thread "main" java.lang.NoClassDefFoundErro ...

  6. Caused by: java.lang.NoClassDefFoundError:

    tomcat启动不了 报错信息头如下: Caused by: java.lang.NoClassDefFoundError: at java.lang.Class.getDeclaredMethods ...

  7. java.lang.NoClassDefFoundError 错误

    练习jfianl,,,配置数据库插件的时候遇到: java.lang.NoClassDefFoundError: com/mchange/v2/c3p0/ComboPooledDataSource 解 ...

  8. 关于怎么解决java.lang.NoClassDefFoundError错误

    五一在部署新的统一登录时,遇到这样一个问题: 很容易把java.lang.NoClassDefFoundError和java.lang.ClassNotfoundException这两个错误搞混,事实 ...

  9. ClassLoader,Thread.currentThread().setContextClassLoader,tomcat的ClassLoader

    实际上,在Java应用中所有程序都运行在线程里,如果在程序中没有手工设置过ClassLoader,对于一般的java类如下两种方法获得的ClassLoader通常都是同一个 this.getClass ...

随机推荐

  1. 关于前端ajax请求url为何添加一个随机数

    一.起因 我在做爬虫的时候发现很多网站上都在url上加一个随机数或者时间戳一开始我以为是啥加密后面发现其实他在后台解析的时候也不需要不排除有些网站他反爬就需要一个时间戳,那他加个随机数是做啥子 二.查 ...

  2. 安装Yapi时,出现json-schema-faker模块找不到问题

    今天换了一台机器按以前的方式安装Yapi工具时,竟然出现了错误. 一 安装yapi时,出现了下面的报错,一开始以为没安装json-schema-faker模块.后面通过找资料,发现是权限问题, 使用c ...

  3. python time包中的time.time()和time.clock()的区别

    在统计python代码 执行速度时要使用到time包,在查找相关函数时有time.time()和time.clock()两个函数可供选择.而两者是有区别的: cpu 的运行机制:cpu是多任务的,例如 ...

  4. Python基础21

    对轴0,轴1,“axis”轴的理解很关键

  5. Java基础—实现多线程的三种方法

    Java虚拟机(JVM,是运行所有Java程序的抽象计算机,是Java语言的运行环境)允许应用程序并发地运行多个线程.在Java语言中,多线程的实现一般有以下三种方法: 1.实现Runnable接口, ...

  6. python如何通过windows命令行运行一个python程序文件?

    python如何通过windows命令行运行一个python程序文件? cmd 进入到py文件对应目录下或者直接在上面的文件地址栏输入cmd,敲入回车 定位到对应的目录下 输入python xxx.p ...

  7. vue-cli3.0创建项目之完成登录页面

    借鉴博客:https://www.cnblogs.com/KenFine/p/10850386.html 接着上一个创建的新项目vue-mydemo01来: 1.创建一个login.vue组件页面:如 ...

  8. Fundebug后端Node.js插件更新至0.2.0,支持监控Express慢请求

    摘要: 性能问题也是BUG,也需要监控. Fundebug后端Node.js异常监控服务 Fundebug是专业的应用异常监控平台,我们Node.js插件fundebug-nodejs可以提供全方位的 ...

  9. 多线程学习笔记(三) BackgroundWorker 暂停/继续

    BackgroundWorker bw; private ManualResetEvent manualReset = new ManualResetEvent(true); private void ...

  10. Django 练习班级管理系统二 -- 添加班级数据

    在上一篇中(Django 练习班级管理系统一 https://www.cnblogs.com/klvchen/p/11078174.html),使用的是莫泰对话框的方式提交数据,适用于数据量少的操作. ...