預設的三個類加載器
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();
“雙親委托”的作用
之是以采用“雙親委托”這種方式主要是為了安全性,避免使用者自己編寫的類動态替換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 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)來顯示的設定。
為什麼要有contex 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就可以了。