java类加载-ClassLoader双亲委派机制
“類加載體系”及ClassLoader雙親委派機(jī)制。java程序中的 .java文件編譯完會(huì)生成 .class文件,而 .class文件就是通過(guò)被稱為類加載器的ClassLoader加載的,而ClassLoder在加載過(guò)程中會(huì)使用“雙親委派機(jī)制”來(lái)加載 .class文件,先上圖:
看著圖從上往下介紹:
BootStrapClassLoader:啟動(dòng)類加載器,該ClassLoader是jvm在啟動(dòng)時(shí)創(chuàng)建的,用于加載?$JAVA_HOME/jre/lib下面的類庫(kù)(或者通過(guò)參數(shù)-Xbootclasspath指定)。由于引導(dǎo)類加載器涉及到虛擬機(jī)本地實(shí)現(xiàn)細(xì)節(jié),開(kāi)發(fā)者無(wú)法直接獲取到啟動(dòng)類加載器的引用,所以不能直接通過(guò)引用進(jìn)行操作。
ExtClassLoader:擴(kuò)展類加載器,該ClassLoader是在sun.misc.Launcher里作為一個(gè)內(nèi)部類ExtClassLoader定義的(即?sun.misc.Launcher$ExtClassLoader),ExtClassLoader會(huì)加載?$JAVA_HOME/jre/lib/ext下的類庫(kù)(或者通過(guò)參數(shù)-Djava.ext.dirs指定)。
AppClassLoader:應(yīng)用程序類加載器,該ClassLoader同樣是在sun.misc.Launcher里作為一個(gè)內(nèi)部類AppClassLoader定義的(即?sun.misc.Launcher$AppClassLoader),AppClassLoader會(huì)加載java環(huán)境變量CLASSPATH所指定的路徑下的類庫(kù),而CLASSPATH所指定的路徑可以通過(guò)System.getProperty("java.class.path")獲取;當(dāng)然,該變量也可以覆蓋,可以使用參數(shù)-cp,例如:java -cp 路徑 (可以指定要執(zhí)行的class目錄)。
CustomClassLoader:自定義類加載器,該ClassLoader是指我們自定義的ClassLoader,比如tomcat的StandardClassLoader屬于這一類;當(dāng)然,大部分情況下使用AppClassLoader就足夠了。
ClassLoader初始化源碼
下面貼下jdk關(guān)于類加載的源碼,上述四種類加載器中CustomClassLoader是用戶自定義的,BootStrapClassLoader是jvm創(chuàng)建的,就不展示了;這里展示下AppClassLoader和ExtClassLoader的啟動(dòng)過(guò)程,前面介紹過(guò),AppClassLoader和ExtClassLoader都是在sun.misc.Launcher里定義的,而我的sun.misc.Launcher沒(méi)有源碼,大家將就看看反編譯的代碼吧。如果想看sun.*包下的類源碼,大家可以下載openjdk來(lái)查看。?
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構(gòu)造函數(shù)的執(zhí)行過(guò)程如下:
通過(guò)ExtClassLoader.getExtClassLoader()創(chuàng)建了ExtClassLoader;
通過(guò)AppClassLoader.getAppClassLoader(ExtClassLoader)創(chuàng)建了AppClassLoader,并將ExtClassLoader設(shè)為AppClassLoader的parent?ClassLoader;
通過(guò)Thread.currentThread().setContextClassLoader(loader)把AppClassLoader設(shè)為線程的上下文 ClassLoader;
根據(jù)jvm參數(shù)-Djava.security.manager創(chuàng)建安全管理器(安全管理器的相關(guān)內(nèi)容會(huì)在后續(xù)博客安全管理器及Java API中介紹),此時(shí)jvm會(huì)設(shè)置系統(tǒng)屬性"java.security.manager"為空字符串""。
再貼下ExtClassLoader源碼:?
1 static class ExtClassLoader extends URLClassLoader {2 private File[] dirs;3 4 public static ExtClassLoader getExtClassLoader() throws IOException {5 // 用調(diào)getExtDirs()方法取獲配置的擴(kuò)展類路徑6 final File[] dirs = getExtDirs();7 try {8 // 應(yīng)用getExtDirs()方法返回的路徑生成一個(gè)新的ClassLoader實(shí)例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 // 再看這個(gè)方法 25 private static File[] getExtDirs() { 26 // 取獲配置的擴(kuò)展類路徑 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 }?
?
反編譯的源碼,大家將就看下;這里大家關(guān)注下getExtDirs()這個(gè)方法,它會(huì)獲取屬性"java.ext.dirs"所對(duì)應(yīng)的值,然后通過(guò)系統(tǒng)分隔符分割,然后加載分割后的字符串對(duì)應(yīng)的目錄作為ClassLoader的類加載庫(kù)。
下面看看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"對(duì)應(yīng)的屬性,并轉(zhuǎn)換為URL[]并設(shè)置為ClassLoader的類加載庫(kù),注意這里的方法入?yún)lassloader就是ExtClassLoader,在創(chuàng)AppClassLoader會(huì)傳入ExtClassLoader作為parent?ClassLoader。
上面就是ClassLoader的啟動(dòng)和初始化過(guò)程,后面會(huì)把loader作為應(yīng)用程序的默認(rèn)ClassLoader使用,看下面的測(cè)試用例:
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的層次結(jié)構(gòu),輸出結(jié)果為:
ClassLoader雙親委派機(jī)制源碼
前面談到了ClassLoader的幾類加載器,而ClassLoader使用雙親委派機(jī)制來(lái)加載class文件的。
ClassLoader的雙親委派機(jī)制是這樣的(這里先忽略掉自定義類加載器CustomClassLoader):
當(dāng)AppClassLoader加載一個(gè)class時(shí),它首先不會(huì)自己去嘗試加載這個(gè)類,而是把類加載請(qǐng)求委派給父類加載器ExtClassLoader去完成。
當(dāng)ExtClassLoader加載一個(gè)class時(shí),它首先也不會(huì)自己去嘗試加載這個(gè)類,而是把類加載請(qǐng)求委派給BootStrapClassLoader去完成。
如果BootStrapClassLoader加載失敗(例如在$JAVA_HOME/jre/lib里未查找到該class),會(huì)使用ExtClassLoader來(lái)嘗試加載;
若ExtClassLoader也加載失敗,則會(huì)使用AppClassLoader來(lái)加載,如果AppClassLoader也加載失敗,則會(huì)報(bào)出異常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 loaded3 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),沒(méi)有的話就判斷有沒(méi)有parent,有的話就用parent來(lái)遞歸的loadClass,然而ExtClassLoader并沒(méi)有設(shè)置parent,則會(huì)通過(guò)findBootstrapClassOrNull來(lái)加載class,而findBootstrapClassOrNull則會(huì)通過(guò)JNI方法”private native Class findBootstrapClass(String name)“來(lái)使用BootStrapClassLoader來(lái)加載class。
然后如果parent未找到class,則會(huì)調(diào)用findClass來(lái)加載class,findClass是一個(gè)protected的空方法,可以覆蓋它以便自定義class加載過(guò)程。
另外,雖然ClassLoader加載類是使用loadClass方法,但是鼓勵(lì)用 ClassLoader 的子類重寫 findClass(String),而不是重寫loadClass,這樣就不會(huì)覆蓋了類加載默認(rèn)的雙親委派機(jī)制。
雙親委派機(jī)制為什么安全
前面談到雙親委派機(jī)制是為了安全而設(shè)計(jì)的,但是為什么就安全了呢?舉個(gè)例子,ClassLoader加載的class文件來(lái)源很多,比如編譯器編譯生成的class、或者網(wǎng)絡(luò)下載的字節(jié)碼。而一些來(lái)源的class文件是不可靠的,比如我可以自定義一個(gè)java.lang.Integer類來(lái)覆蓋jdk中默認(rèn)的Integer類,例如下面這樣:
1 package java.lang;2 3 /**4 * hack5 */6 public class Integer {7 public Integer(int value) {8 System.exit(0);9 } 10 }?
初始化這個(gè)Integer的構(gòu)造器是會(huì)退出JVM,破壞應(yīng)用程序的正常進(jìn)行,如果使用雙親委派機(jī)制的話該Integer類永遠(yuǎn)不會(huì)被調(diào)用,以為委托BootStrapClassLoader加載后會(huì)加載JDK中的Integer類而不會(huì)加載自定義的這個(gè),可以看下下面這測(cè)試個(gè)用例:
1 public static void main(String... args) { 2 Integer i = new Integer(1); 3 System.err.println(i); 4 }執(zhí)行時(shí)JVM并未在new Integer(1)時(shí)退出,說(shuō)明未使用自定義的Integer,于是就保證了安全性。
總結(jié)
以上是生活随笔為你收集整理的java类加载-ClassLoader双亲委派机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 如何提高用户注册转化与用户激活
- 下一篇: 全面解读java虚拟机