##預設的三個類載入器
Java預設是有三個ClassLoader,按層次關係從上到下依次是:
- Bootstrap ClassLoader
- Ext ClassLoader
- System ClassLoader
Bootstrap ClassLoader是最頂層的ClassLoader,它比較特殊,是用C++編寫整合在JVM中的,是JVM啟動的時候用來載入一些核心類的,比如:`rt.jar`,`resources.jar`,`charsets.jar`,`jce.jar`等,可以執行下面程式碼看都有哪些:
```
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i].toExternalForm());
}
```
其餘兩個ClassLoader都是繼承自`ClassLoader`這個類。Java的類載入採用了一種叫做“雙親委託”的方式(稍後解釋),所以除了`Bootstrap ClassLoader`其餘的ClassLoader都有一個“父”類載入器, 不是通過繼承,而是一種包含的關係。
```
//ClassLoader.java
public abstract class ClassLoader {
...
// The parent class loader for delegation
private ClassLoader parent;
...
```
##“雙親委託”
所謂“雙親委託”就是當載入一個類的時候會先委託給父類載入器去載入,當父類載入器無法載入的時候再嘗試自己去載入,所以整個類的載入是“自上而下”的,如果都沒有載入到則丟擲`ClassNotFoundException`異常。
上面提到Bootstrap ClassLoader是最頂層的類載入器,實際上Ext ClassLoader和System ClassLoader就是一開始被它載入的。
Ext ClassLoader稱為擴充套件類載入器,負責載入Java的擴充套件類庫,預設載入JAVA_HOME/jre/lib/ext/目錄下的所有的jar(包括自己手動放進去的jar包)。
System ClassLoader叫做系統類載入器,負責載入應用程式classpath目錄下的所有jar和class檔案,包括我們平時執行jar包指定cp引數下的jar包。
執行下面的程式碼可以驗證上面內容:
```
ClassLoader loader = Debug.class.getClassLoader();
while(loader != null) {
System.out.println(loader);
loader = loader.getParent();
}
System.out.println(loader);
```
##“雙親委託”的作用
之所以採用“雙親委託”這種方式主要是為了安全性,避免使用者自己編寫的類動態替換Java的一些核心類,比如String,同時也避免了重複載入,因為JVM中區分不同類,不僅僅是根據類名,相同的class檔案被不同的ClassLoader載入就是不同的兩個類,如果相互轉型的話會拋`java.lang.ClassCaseException`.
##自定義類載入器
除了上面說的三種預設的類載入器,使用者可以通過繼承`ClassLoader`類來建立自定義的類載入器,之所以需要自定義類載入器是因為有時候我們需要通過一些特殊的途徑建立類,比如網路。
至於自定義類載入器是如何發揮作用的,`ClassLoader`類的loadClass方法已經把演算法定義了:
```
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) {
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.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
```
>1. Invoke `findLoadedClass(String)` to check if the class has already been loaded.
>2. Invoke the loadClass method on the parent class loader. If the parent is null the class loader built-in to the virtual machine is used, instead.
>3. Invoke the `findClass(String)` method to find the class.
看上面的Javadoc可以知道,自定義的類載入器只要過載`findClass`就好了。
##Context ClassLoader
首先Java中ClassLoader就上面提到的四種,`Bootstrap ClassLoader`,`Ext ClassLoader`,`System ClassLoader`以及使用者自定義的,所以`Context ClassLoader`並不是一種新的類載入器,肯定是這四種的一種。
首先關於類的載入補充一點就是如果類A是被一個載入器載入的,那麼類A中引用的B也是由這個載入器載入的(如果B還沒有被載入的話),通常情況下就是類B必須在類A的classpath下。
但是考慮多執行緒環境下不同的物件可能是由不同的ClassLoader載入的,那麼當一個由ClassLoaderC載入的物件A從一個執行緒被傳到另一個執行緒ThreadB中,而ThreadB是由ClassLoaderD載入的,這時候如果A想獲取除了自己的classpath以外的資源的話,它就可以通過`Thread.currentThread().getContextClassLoader()`來獲取執行緒上下文的ClassLoader了,一般就是ClassLoaderD了,可以通過`Thread.currentThread().setContextClassLoader(ClassLoader)`來顯示的設定。
##為什麼要有Context ClassLoader
之所以有Context ClassLoader是因為Java的這種“雙親委託”機制是有侷限性的:
- 舉網上的一個例子:
> JNDI為例,JNDI的類是由bootstrap ClassLoader從rt.jar中間載入的,但是JNDI具體的核心驅動是由正式的實現提供的,並且通常會處於-cp引數之下(注:也就是預設的System ClassLoader管理),這就要求bootstartp ClassLoader去載入只有SystemClassLoader可見的類,正常的邏輯就沒辦法處理。怎麼辦呢?parent可以通過獲得當前呼叫Thread的方法獲得呼叫執行緒的>Context ClassLoder 來載入類。
- 我上面提到的載入資源的例子。
`Contex ClassLoader`提供了一個突破這種機制的後門。
Context ClassLoader一般在一些框架程式碼中用的比較多,平時寫程式碼的時候用類的ClassLoader就可以了。
##參考連結
[http://stackoverflow.com/questions/1771679/difference-between-threads-context-class-loader-and-normal-classloader][1]
[1]: http://stackoverflow.com/questions/1771679/difference-between-threads-context-class-loader-and-normal-classloader