java類加載-ClassLoader雙親委派機制


“類加載體系”及ClassLoader雙親委派機制。java程序中的 .java文件編譯完會生成 .class文件,而 .class文件就是通過被稱為類加載器的ClassLoader加載的,而ClassLoder在加載過程中會使用“雙親委派機制”來加載 .class文件,先上圖:

看着圖從上往下介紹:

  1. BootStrapClassLoader:啟動類加載器,該ClassLoader是jvm在啟動時創建的,用於加載 $JAVA_HOME/jre/lib下面的類庫(或者通過參數-Xbootclasspath指定)。由於引導類加載器涉及到虛擬機本地實現細節,開發者無法直接獲取到啟動類加載器的引用,所以不能直接通過引用進行操作。

  2. ExtClassLoader:擴展類加載器,該ClassLoader是在sun.misc.Launcher里作為一個內部類ExtClassLoader定義的(即 sun.misc.Launcher$ExtClassLoader),ExtClassLoader會加載 $JAVA_HOME/jre/lib/ext下的類庫(或者通過參數-Djava.ext.dirs指定)。

  3. AppClassLoader:應用程序類加載器,該ClassLoader同樣是在sun.misc.Launcher里作為一個內部類AppClassLoader定義的(即 sun.misc.Launcher$AppClassLoader),AppClassLoader會加載java環境變量CLASSPATH所指定的路徑下的類庫,而CLASSPATH所指定的路徑可以通過System.getProperty("java.class.path")獲取;當然,該變量也可以覆蓋,可以使用參數-cp,例如:java -cp 路徑 (可以指定要執行的class目錄)。

  4. CustomClassLoader:自定義類加載器,該ClassLoader是指我們自定義的ClassLoader,比如tomcat的StandardClassLoader屬於這一類;當然,大部分情況下使用AppClassLoader就足夠了。

ClassLoader初始化源碼

下面貼下jdk關於類加載的源碼,上述四種類加載器中CustomClassLoader是用戶自定義的,BootStrapClassLoader是jvm創建的,就不展示了;這里展示下AppClassLoader和ExtClassLoader的啟動過程,前面介紹過,AppClassLoader和ExtClassLoader都是在sun.misc.Launcher里定義的,而我的sun.misc.Launcher沒有源碼,大家將就看看反編譯的代碼吧。如果想看sun.*包下的類源碼,大家可以下載openjdk來查看。 

 1 public Launcher(){
 2     ExtClassLoader extclassloader;
 3     try{
 4         extclassloader = ExtClassLoader.getExtClassLoader();
 5     }
 6     catch(IOException ioexception) {
 7         throw new InternalError("Could not create extension class loader");
 8     }
 9     try{
10         loader = AppClassLoader.getAppClassLoader(extclassloader);
11     }
12     catch(IOException ioexception1){
13         throw new InternalError("Could not create application class loader");
14     }
15     Thread.currentThread().setContextClassLoader(loader);
16     String s = System.getProperty("java.security.manager");
17     if(s != null){
18         SecurityManager securitymanager = null;
19         if("".equals(s) || "default".equals(s))
20         securitymanager = new SecurityManager();
21         else
22         try{
23             securitymanager = (SecurityManager)loader.loadClass(s).newInstance();
24         }
25         catch(IllegalAccessException illegalaccessexception) { }
26         catch(InstantiationException instantiationexception) { }
27         catch(ClassNotFoundException classnotfoundexception) { }
28         catch(ClassCastException classcastexception) { }
29         if(securitymanager != null)
30           System.setSecurityManager(securitymanager);
31         else
32           throw new InternalError((new StringBuilder()).append("Could not create SecurityManager: ").append(s).toString());
33     }
34 }

 

可以看到在Launcher構造函數的執行過程如下:

  1. 通過ExtClassLoader.getExtClassLoader()創建了ExtClassLoader;

  2. 通過AppClassLoader.getAppClassLoader(ExtClassLoader)創建了AppClassLoader,並將ExtClassLoader設為AppClassLoader的parent ClassLoader;

  3. 通過Thread.currentThread().setContextClassLoader(loader)把AppClassLoader設為線程的上下文 ClassLoader;

  4. 根據jvm參數-Djava.security.manager創建安全管理器(安全管理器的相關內容會在后續博客安全管理器及Java API中介紹),此時jvm會設置系統屬性"java.security.manager"為空字符串""。

再貼下ExtClassLoader源碼: 

 1 static class ExtClassLoader extends URLClassLoader {
 2     private File[] dirs;
 3  
 4     public static ExtClassLoader getExtClassLoader() throws IOException {
 5         // 用調getExtDirs()方法取獲配置的擴展類路徑
 6         final File[] dirs = getExtDirs();
 7         try {
 8             // 應用getExtDirs()方法返回的路徑生成一個新的ClassLoader實例
 9             return (ExtClassLoader) AccessController.doPrivileged(new PrivilegedExceptionAction() {
10                 public Object run() throws IOException {
11                     int len = dirs.length;
12                     for (int i = 0; i < len; i++) {
13                         MetaIndex.registerDirectory(dirs[i]);
14                     }
15                     return new ExtClassLoader(dirs);
16                 }
17             });
18         } catch (java.security.PrivilegedActionException e) {
19             throw (IOException) e.getException();
20         }
21     }
22 
23 
24     // 再看這個方法
25     private static File[] getExtDirs() {
26         // 取獲配置的擴展類路徑
27         String s = System.getProperty("java.ext.dirs");
28         File[] dirs;
29         if (s != null) {
30             StringTokenizer st = new StringTokenizer(s, File.pathSeparator);
31             int count = st.countTokens();
32             dirs = new File[count];
33             for (int i = 0; i < count; i++) {
34                 dirs[i] = new File(st.nextToken());
35             }
36         } else {
37             dirs = new File[0];
38         }
39         return dirs;
40     }
41     
42     // 其他碼代略
43     ...
44 }

 

 

反編譯的源碼,大家將就看下;這里大家關注下getExtDirs()這個方法,它會獲取屬性"java.ext.dirs"所對應的值,然后通過系統分隔符分割,然后加載分割后的字符串對應的目錄作為ClassLoader的類加載庫。

下面看看AppClassLoader源碼:

 1 public static ClassLoader getAppClassLoader(ClassLoader classloader) throws IOException{
 2     String s = System.getProperty("java.class.path");
 3     File afile[] = s != null ? Launcher.getClassPath(s) : new File[0];
 4     return (AppClassLoader)AccessController.doPrivileged(new PrivilegedAction(s, afile, classloader) {
 5     public Object run() {
 6         URL aurl[] = s != null ? Launcher.pathToURLs(path) : new URL[0];
 7         return new AppClassLoader(aurl, extcl);
 8     }
 9 
10     final String val$s;
11     final File val$path[];
12     final ClassLoader val$extcl;
13 
14     {
15         s = s1;
16         path = afile;
17         extcl = classloader;
18         super();
19     }
20     });
21 }

 

 

首先獲取"java.class.path"對應的屬性,並轉換為URL[]並設置為ClassLoader的類加載庫,注意這里的方法入參classloader就是ExtClassLoader,在創AppClassLoader會傳入ExtClassLoader作為parent ClassLoader。

上面就是ClassLoader的啟動和初始化過程,后面會把loader作為應用程序的默認ClassLoader使用,看下面的測試用例:

1 public static void main(String... args) {
2     ClassLoader loader = Test.class.getClassLoader();
3     System.err.println(loader);
4     while (loader != null) {
5         loader = loader.getParent();
6         System.err.println(loader);
7     }
8 }

 

可以看到ClassLoader的層次結構,輸出結果為:

ClassLoader雙親委派機制源碼

前面談到了ClassLoader的幾類加載器,而ClassLoader使用雙親委派機制來加載class文件的。

ClassLoader的雙親委派機制是這樣的(這里先忽略掉自定義類加載器CustomClassLoader):

  1. 當AppClassLoader加載一個class時,它首先不會自己去嘗試加載這個類,而是把類加載請求委派給父類加載器ExtClassLoader去完成。

  2. 當ExtClassLoader加載一個class時,它首先也不會自己去嘗試加載這個類,而是把類加載請求委派給BootStrapClassLoader去完成。

  3. 如果BootStrapClassLoader加載失敗(例如在$JAVA_HOME/jre/lib里未查找到該class),會使用ExtClassLoader來嘗試加載;

  4. 若ExtClassLoader也加載失敗,則會使用AppClassLoader來加載,如果AppClassLoader也加載失敗,則會報出異常ClassNotFoundException。

下面貼下ClassLoader的loadClass(String name, boolean resolve)源碼:

 1 protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
 2     // First, check if the class has already been loaded
 3     Class c = findLoadedClass(name);
 4     if (c == null) {
 5         try {
 6             if (parent != null) {
 7                 c = parent.loadClass(name, false);
 8             } else {
 9                 c = findBootstrapClassOrNull(name);
10             }
11         } catch (ClassNotFoundException e) {
12             // ClassNotFoundException thrown if class not found
13             // from the non-null parent class loader
14         }
15         if (c == null) {
16             // If still not found, then invoke findClass in order
17             // to find the class.
18             c = findClass(name);
19         }
20     }
21     if (resolve) {
22         resolveClass(c);
23     }
24     return c;
25 }

 

代碼很明朗:首先找緩存(findLoadedClass),沒有的話就判斷有沒有parent,有的話就用parent來遞歸的loadClass,然而ExtClassLoader並沒有設置parent,則會通過findBootstrapClassOrNull來加載class,而findBootstrapClassOrNull則會通過JNI方法”private native Class findBootstrapClass(String name)“來使用BootStrapClassLoader來加載class。

然后如果parent未找到class,則會調用findClass來加載class,findClass是一個protected的空方法,可以覆蓋它以便自定義class加載過程。

另外,雖然ClassLoader加載類是使用loadClass方法,但是鼓勵用 ClassLoader 的子類重寫 findClass(String),而不是重寫loadClass,這樣就不會覆蓋了類加載默認的雙親委派機制。

雙親委派機制為什么安全

前面談到雙親委派機制是為了安全而設計的,但是為什么就安全了呢?舉個例子,ClassLoader加載的class文件來源很多,比如編譯器編譯生成的class、或者網絡下載的字節碼。而一些來源的class文件是不可靠的,比如我可以自定義一個java.lang.Integer類來覆蓋jdk中默認的Integer類,例如下面這樣:

 1 package java.lang;
 2 
 3 /**
 4  * hack
 5  */
 6 public class Integer {
 7     public Integer(int value) {
 8         System.exit(0);
 9     }
10 }

 

初始化這個Integer的構造器是會退出JVM,破壞應用程序的正常進行,如果使用雙親委派機制的話該Integer類永遠不會被調用,以為委托BootStrapClassLoader加載后會加載JDK中的Integer類而不會加載自定義的這個,可以看下下面這測試個用例:

1 public static void main(String... args) {
2     Integer i = new Integer(1);
3     System.err.println(i);
4 }

執行時JVM並未在new Integer(1)時退出,說明未使用自定義的Integer,於是就保證了安全性。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM