Thread.currentThread().setContextClassLoader爲何不生效與java.lang.NoClassDefFoundError之Java類加載的Parent fir

衆所周知,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.htmlhtml

相關文章
相關標籤/搜索