URLClassLoader詳解
ClassLoader翻譯過來就是類加載器,普通的java開發者其實用到的不多,但對於某些框架開發者來說卻非常常見。理解ClassLoader的加載機制,也有利於我們編寫出更高效的代碼。ClassLoader的具體作用就是將class文件加載到jvm虛擬機中去,程序就可以正確運行了。但是,jvm啓動的時候,並不會一次性加載所有的class文件,而是根據需要去動態加載。想想也是的,一次性加載那麼多jar包那麼多class,那內存不崩潰。本文的目的也是學習ClassLoader這種加載機制。。
- java類加載機制原理
- 雙親加載機制的優劣
java類加載機制
ClassLoader結構
java中內置了很多類加載器,本文只討論幾個核心類加載器:
ClassLoader:所有類加載器的基類,它是抽象的,定義了類加載最核心的操作。所有繼承與classloader的加載器,都會優先判斷是否被父類加載器加載過,防止多次加載,防止加載衝突。。。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//鎖,防止多次加載,所以jvm啓動巨慢
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
jdk 1.7爲了提供並行加載class,提供ClassLoader.ParallelLoaders內部類,用來封裝一組並行能力的加載器類型。這個一般是用不到的,有興趣可以先看一下。但是需要知道ClassLoader是支持並行加載的。
private static class ParallelLoaders
Bootstrap classLoader:位於java.lang.classload,所有的classload都要經過這個classload判斷是否已經被加載過,採用native code實現,是JVM的一部分,主要加載JVM自身工作需要的類,如java.lang.、java.uti.等; 這些類位於$JAVA_HOME/jre/lib/rt.jar。Bootstrap ClassLoader不繼承自ClassLoader,因爲它不是一個普通的Java類,底層由C++編寫,已嵌入到了JVM內核當中,當JVM啓動後,Bootstrap ClassLoader也隨着啓動,負責加載完核心類庫後,並構造Extension ClassLoader和App ClassLoader類加載器。
/**
* Returns a class loaded by the bootstrap class loader;
* or return null if not found.
*/
private Class<?> findBootstrapClassOrNull(String name)
{
if (!checkName(name)) return null;
return findBootstrapClass(name);
}
// return null if not found
private native Class<?> findBootstrapClass(String name);
SecureClassLoader:繼承自ClassLoader,添加了關聯類源碼、關聯繫統policy權限等支持。
public class SecureClassLoader extends ClassLoader
URLClassLoader:繼承自SecureClassLoader,支持從jar文件和文件夾中獲取class,繼承於classload,加載時首先去classload裏判斷是否由bootstrap classload加載過,1.7 新增實現closeable接口,實現在try 中自動釋放資源,但撲捉不了.close()異常
public class URLClassLoader extends SecureClassLoader implements Closeable
ExtClassLoader:擴展類加載器,繼承自URLClassLoader繼承於urlclassload,擴展的class loader,加載位於$JAVA_HOME/jre/lib/ext目錄下的擴展jar。查看源碼可知其查找範圍爲System.getProperty(“java.ext.dirs”)。
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
//System.getProperty("java.ext.dirs");
//在項目啓動時就加載所有的ext.dirs目錄下的文件,並將其初始化
final File[] var0 = getExtDirs();
try {
//AccessController.doPrivileged特權,讓程序突破當前域權限限制,臨時擴大訪問權限
return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
public Launcher.ExtClassLoader run() throws IOException {
int var1 = var0.length;
for(int var2 = 0; var2 < var1; ++var2) {
MetaIndex.registerDirectory(var0[var2]);
}
return new Launcher.ExtClassLoader(var0);
}
});
} catch (PrivilegedActionException var2) {
throw (IOException)var2.getException();
}
}
ExtClassLoader 比較有意思的是,他使用的是頂級類(classloader)的loadclass方法,並沒有重寫,而且他的父親加載器是null。。 。
public ExtClassLoader(File[] var1) throws IOException {
//parents is null
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
AppClassLoader:應用類加載器,繼承自URLClassLoader,也叫系統類加載器(ClassLoader.getSystemClassLoader()可得到它),它負載加載應用的classpath下的類,查找範圍System.getProperty(“java.class.path”),通過-cp或-classpath指定的類都會被其加載,沒有完全遵循雙親委派模型的,它重的是loadClass方法
public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
int var3 = var1.lastIndexOf(46);
if (var3 != -1) {
SecurityManager var4 = System.getSecurityManager();
if (var4 != null) {
var4.checkPackageAccess(var1.substring(0, var3));
}
}
//ucp是SharedSecrets獲取的Java棧幀中存儲的類信息
if (this.ucp.knownToNotExist(var1)) {
//頂級類classloader加載的信息
Class var5 = this.findLoadedClass(var1);
if (var5 != null) {
if (var2) {
//link過程,Class載入必須link,link指的是把單一的Class加入到有繼承關係的類樹中,不link一切都無從談起了
this.resolveClass(var5);
}
return var5;
} else {
throw new ClassNotFoundException(var1);
}
} else {
return super.loadClass(var1, var2);
}
}
Launcher:java程序入口,負責實例化相關class,ExtClassLoader和AppClassLoader都是其內部實現類。。。
首先獲取bootClassPath 的位置信息,交付於父類Classloader去加載位於$JAVA_HOME/jre/lib/rt.jar 的類
private static String bootClassPath = System.getProperty("sun.boot.class.path");
public static URLClassPath getBootstrapClassPath() {
return Launcher.BootClassPathHolder.bcp;
}
實例化相關ExtClassLoader和AppClassLoader,單線程運行,內部加載會對class文件加鎖(所以啓動慢的原因??)
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//實例化ExtClassLoader,
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//實例化AppClassLoader,並把ExtClassLoader置爲父類
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
//爲線程設置上下文加載器,防止線程多次加載類
Thread.currentThread().setContextClassLoader(this.loader);
//加載安全管理
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
;
} catch (InstantiationException var6) {
;
} catch (ClassNotFoundException var7) {
;
} catch (ClassCastException var8) {
;
}
} else {
var3 = new SecurityManager();
}
if (var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
ClassLoader運行結構
主要基於 雙親加載機制
雙親加載機制:主要體現在ClassLoader的loadClass()方法中,思路很簡單:先檢查是否已經被加載過,若沒有加載則調用父類加載器的loadClass()方法,若父類加載器爲空則默認使用啓動類加載器作爲父類加載器。如果父類加載器加載失敗,拋出ClassNotFoundException異常後,調用自己的findClass()方法進行加載。
雙親與判斷等於:一般自定義的Class Loader可以從java.lang.ClassLoader繼承,不同classloader加載相同的類,他們在內存也不是相等的,即它們不能互相轉換,會直接拋異常。java.lang.ClassLoader的核心加載方法是loadClass方法
Class clazz = null;
ClassLoader classLoader;
try {
classLoader = new SpecialClassLoader ();
clazz = classLoader.loadClass("Hello");
System.out.println(clazz);
System.out.println(clazz.getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Class helloClazz;
helloClazz = Hello.class;
System.out.println("helloClazz:" + helloClazz);
System.out.println(helloClazz.getClassLoader());
System.out.println(helloClazz == clazz);
System.out.println(helloClazz.equals(clazz));
運行結果:
loadClass:Hello
specialLoadClass:test/Hello.class
loadClass:java.lang.Object
—resolveClass–
class com.test.javatechnology.classloader.test.Hello
com.test.javatechnology.classloader.SpecialClassLoader@106d69c
helloClazz:class com.test.javatechnology.classloader.test.Hello
sun.misc.Launcher$AppClassLoader@e2f2a
false
false