java有兩種類型的classload,一種是user-defined的,一種是jvm內置的bootstrap class loader,所有user-defined的class loader都是java.lang.ClassLoader的子類.
而jvm內置的class loader有3種,分別是 Bootstrap ClassLoader, Extension ClassLoader(即ExtClassLoader),System ClassLoader(即AppClassLoader).
而jvm加載時的雙親委派 就不說了,javaeye上有很多文章都有介紹..
可以分別看一下他們的構造器,其中Bootstrap ClassLoader是用c寫的.
java.lang.ClassLoader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
protected ClassLoader(ClassLoader parent) { SecurityManager security = System.getSecurityManager(); if (security != null ) { security.checkCreateClassLoader(); } //給父loader賦值. this .parent = parent; initialized = true ; } protected ClassLoader() { SecurityManager security = System.getSecurityManager(); if (security != null ) { security.checkCreateClassLoader(); } //這邊將會把AppClassLoader付給父loader this .parent = getSystemClassLoader(); initialized = true ; } |
這個構造器有帶參數的,和不帶構造器的2個。帶參數的構造器傳入的是這個 class loader的父loader,而不帶參數的構造器則會把getSystemClassLoader()所返回的class loader當作他自己的父裝載器.下面我們來看getSystemClassLoader()的代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
public static ClassLoader getSystemClassLoader() { //所返回的class loader在這個方法里面被賦值 initSystemClassLoader(); if (scl == null ) { return null ; } SecurityManager sm = System.getSecurityManager(); if (sm != null ) { ClassLoader ccl = getCallerClassLoader(); if (ccl != null && ccl != scl && !scl.isAncestor(ccl)) { sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION); } } return scl; } private static synchronized void initSystemClassLoader() { if (!sclSet) { if (scl != null ) throw new IllegalStateException( "recursive invocation" ); sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); if (l != null ) { Throwable oops = null ; //在這邊被賦值 scl = l.getClassLoader(); ................................... ....................................... } } sclSet = true ; } } |
這邊的父class loader就是scl,也就是l.getClassLoader()所得到的,getClassLoader(),接下來看Launcher的源碼:
private static Launcher launcher = new Launcher();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
public static Launcher getLauncher() { return launcher; } private ClassLoader loader; public Launcher() { // Create the extension class loader ClassLoader extcl; try { //這里傳入構造器的parent為空 extcl = ExtClassLoader.getExtClassLoader(); } catch (IOException e) { throw new InternalError( "Could not create extension class loader" ); } // Now create the class loader to use to launch the application try { //這邊可以看到默認的loader就是AppClassLoader,也就是說getSystemClassLoader返回的就是AppClassLoader loader = AppClassLoader.getAppClassLoader(extcl); } catch (IOException e) { throw new InternalError( "Could not create application class loader" ); } //每一個當前線程一個classload,以防止多線程中的classload引起的混亂(這個是我自己理解的,呵呵) // Also set the context class loader for the primordial thread. Thread.currentThread().setContextClassLoader(loader); ................................... ................................................ } /* * Returns the class loader used to launch the main application. */ public ClassLoader getClassLoader() { return loader; } |
從中我們看到AppClassLoader的父loader是ExtClassLoader,而ExtClassLoader的父loader是什么呢?我們在來看ExtClassLoader的構造器:
1
2
3
4
|
public ExtClassLoader(File[] dirs) throws IOException { super (getExtURLs(dirs), null , factory); this .dirs = dirs; } |
他的父loader為空,而他的頂級父類是java.lang.ClassLoader而當傳入的parent為null時,我們使用 ExtClassLoader load一個類時,系統會調用Bootstrap ClassLoader.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null ) { try { if (parent != null ) { //先調用父loader來load. c = parent.loadClass(name, false ); } else { //調用Bootstrap ClassLoader來load c = findBootstrapClass0(name); } } catch (ClassNotFoundException e) { // If still not found, then invoke findClass in order // to find the class. c = findClass(name); } } if (resolve) { resolveClass(c); } return c; } |
而這里findBootstrapClass0也就是調用Bootstrap ClassLoader這個最核心的class loader來load class.
最終我們可以看到getSystemClassLoader() 返回的class loader就是AppClassLoader.
Java Classloader機制解析
JDK默認ClassLoader
JDK 默認提供了如下幾種ClassLoader
Bootstrp loader
Bootstrp加載器是用C++語言寫的,它是在Java虛擬機啟動后初始化的,它主要負責加載%JAVA_HOME%/jre/lib,-Xbootclasspath參數指定的路徑以及%JAVA_HOME%/jre/classes中的類。
ExtClassLoader
Bootstrp loader加載ExtClassLoader,并且將ExtClassLoader的父加載器設置為Bootstrp loader.ExtClassLoader是用Java寫的,具體來說就是 sun.misc.Launcher$ExtClassLoader,ExtClassLoader主要加載%JAVA_HOME%/jre/lib/ext,此路徑下的所有classes目錄以及java.ext.dirs系統變量指定的路徑中類庫。
AppClassLoader
Bootstrp loader加載完ExtClassLoader后,就會加載AppClassLoader,并且將AppClassLoader的父加載器指定為 ExtClassLoader。AppClassLoader也是用Java寫成的,它的實現類是 sun.misc.Launcher$AppClassLoader,另外我們知道ClassLoader中有個getSystemClassLoader方法,此方法返回的正是AppclassLoader.AppClassLoader主要負責加載classpath所指定的位置的類或者是jar文檔,它也是Java程序默認的類加載器。
綜上所述,它們之間的關系可以通過下圖形象的描述:
雙親委托模型
Java中ClassLoader的加載采用了雙親委托機制,采用雙親委托機制加載類的時候采用如下的幾個步驟:
當前ClassLoader首先從自己已經加載的類中查詢是否此類已經加載,如果已經加載則直接返回原來已經加載的類。
每個類加載器都有自己的加載緩存,當一個類被加載了以后就會放入緩存,等下次加載的時候就可以直接返回了。
當前classLoader的緩存中沒有找到被加載的類的時候,委托父類加載器去加載,父類加載器采用同樣的策略,首先查看自己的緩存,然后委托父類的父類去加載,一直到bootstrp ClassLoader.
當所有的父類加載器都沒有加載的時候,再由當前的類加載器加載,并將其放入它自己的緩存中,以便下次有加載請求的時候直接返回。
說到這里大家可能會想,Java為什么要采用這樣的委托機制?理解這個問題,我們引入另外一個關于Classloader的概念“命名空間”, 它是指要確定某一個類,需要類的全限定名以及加載此類的ClassLoader來共同確定。也就是說即使兩個類的全限定名是相同的,但是因為不同的 ClassLoader加載了此類,那么在JVM中它是不同的類。明白了命名空間以后,我們再來看看委托模型。采用了委托模型以后加大了不同的 ClassLoader的交互能力,比如上面說的,我們JDK本生提供的類庫,比如hashmap,linkedlist等等,這些類由bootstrp 類加載器加載了以后,無論你程序中有多少個類加載器,那么這些類其實都是可以共享的,這樣就避免了不同的類加載器加載了同樣名字的不同類以后造成混亂。
如何自定義ClassLoader
Java除了上面所說的默認提供的classloader以外,它還容許應用程序可以自定義classloader,那么要想自定義classloader我們需要通過繼承java.lang.ClassLoader來實現,接下來我們就來看看再自定義Classloader的時候,我們需要注意的幾個重要的方法:
1.loadClass 方法
loadClass method declare
1
|
public Class<?> loadClass(String name) throws ClassNotFoundException |
上面是loadClass方法的原型聲明,上面所說的雙親委托機制的實現其實就實在此方法中實現的。下面我們就來看看此方法的代碼來看看它到底如何實現雙親委托的。
loadClass method implement
1
2
3
4
|
public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false ); } |
從上面可以看出loadClass方法調用了loadcClass(name,false)方法,那么接下來我們再來看看另外一個loadClass方法的實現。
Class loadClass(String name, boolean resolve)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class c = findLoadedClass(name); //檢查class是否已經被加載過了 if (c == null) { try { if (parent != null ) { c = parent.loadClass(name, false ); //如果沒有被加載,且指定了父類加載器,則委托父加載器加載。 } else { c = findBootstrapClass0(name); //如果沒有父類加載器,則委托bootstrap加載器加載 } } catch (ClassNotFoundException e) { // If still not found, then invoke findClass in order // to find the class. c = findClass(name); //如果父類加載沒有加載到,則通過自己的findClass來加載。 } } if (resolve) { resolveClass(c); } return c; } |
上面的代碼,我加了注釋通過注釋可以清晰看出loadClass的雙親委托機制是如何工作的。 這里我們需要注意一點就是public Class<?> loadClass(String name) throws ClassNotFoundException沒有被標記為final,也就意味著我們是可以override這個方法的,也就是說雙親委托機制是可以打破的。另外上面注意到有個findClass方法,接下來我們就來說說這個方法到底是搞末子的。
2.findClass
我們查看java.lang.ClassLoader的源代碼,我們發現findClass的實現如下:
1
2
3
4
|
protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); } |
我們可以看出此方法默認的實現是直接拋出異常,其實這個方法就是留給我們應用程序來override的。那么具體的實現就看你的實現邏輯了,你可以從磁盤讀取,也可以從網絡上獲取class文件的字節流,獲取class二進制了以后就可以交給defineClass來實現進一步的加載。defineClass我們再下面再來描述。 ok,通過上面的分析,我們可以得出如下結論:
我們在寫自己的ClassLoader的時候,如果想遵循雙親委托機制,則只需要override findClass.
3.defineClass
我們首先還是來看看defineClass的源碼:
defineClass
1
2
3
4
5
|
protected final Class<?> defineClass(String name, byte [] b, int off, int len) throws ClassFormatError { return defineClass(name, b, off, len, null ); } |
從上面的代碼我們看出此方法被定義為了final,這也就意味著此方法不能被Override,其實這也是jvm留給我們的唯一的入口,通過這個唯 一的入口,jvm保證了類文件必須符合Java虛擬機規范規定的類的定義。此方法最后會調用native的方法來實現真正的類的加載工作。
Ok,通過上面的描述,我們來思考下面一個問題:
假如我們自己寫了一個java.lang.String的類,我們是否可以替換調JDK本身的類?
答案是否定的。我們不能實現。為什么呢?我看很多網上解釋是說雙親委托機制解決這個問題,其實不是非常的準確。因為雙親委托機制是可以打破的,你完全可以自己寫一個classLoader來加載自己寫的java.lang.String類,但是你會發現也不會加載成功,具體就是因為針對java.*開頭的類,jvm的實現中已經保證了必須由bootstrp來加載。
不遵循“雙親委托機制”的場景
上面說了雙親委托機制主要是為了實現不同的ClassLoader之間加載的類的交互問題,被大家公用的類就交由父加載器去加載,但是Java中確實也存在父類加載器加載的類需要用到子加載器加載的類的情況。下面我們就來說說這種情況的發生。
Java中有一個SPI(Service Provider Interface)標準,使用了SPI的庫,比如JDBC,JNDI等,我們都知道JDBC需要第三方提供的驅動才可以,而驅動的jar包是放在我們應 用程序本身的classpath的,而jdbc 本身的api是jdk提供的一部分,它已經被bootstrp加載了,那第三方廠商提供的實現類怎么加載呢?這里面JAVA引入了線程上下文類加載的概 念,線程類加載器默認會從父線程繼承,如果沒有指定的話,默認就是系統類加載器(AppClassLoader),這樣的話當加載第三方驅動的時候,就可 以通過線程的上下文類加載器來加載。
另外為了實現更靈活的類加載器OSGI以及一些Java app server也打破了雙親委托機制。