先簡單介紹下java的classloader,網上資料很多,就說點關鍵的。

  Java 中的類載入器大致可以分成兩類,一類是系統提供的,另外一類則是由 Java 應用開發人員編寫的。系統提供的類載入器主要有下面三個:

  引導類載入器(bootstrap class loader):它用來載入 Java 的核心庫,是用原生程式碼來實現的,並不繼承自 java.lang.ClassLoader。

  擴充套件類載入器(extensions class loader):它用來載入 Java 的擴充套件庫。Java 虛擬機器的實現會提供一個擴充套件庫目錄。該類載入器在此目錄裡面查詢並載入 Java 類。

  系統類載入器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來載入 Java 類。一般來說,Java 應用的類都是由它來完成載入的。可以通過 ClassLoader.getSystemClassLoader() 來獲取它。

  除了系統提供的類載入器以外,開發人員可以通過繼承 java.lang.ClassLoader 類的方式實現自己的類載入器,以滿足一些特殊的需求。

  除了引導類載入器之外,所有的類載入器都有一個父類載入器。類載入採用委託模式,先一層一層交給父類載入,父載入不成功再一層一層轉給子載入。

  要點1:為什麼採用這種委託方式,是為了安全,比如使用者自定義了個java.lang.String,那麼如果不交給引導類載入器去載入的話,記憶體中就會有不止一個String的類例項。而且一個限定包內訪問許可權的內容,黑客也可以用這種方式獲取(要點2再繼續說明)。採用了這種方式的話,引導類載入器只會載入一次類,看見使用者自定義的String來了,就去看自己有沒有載入,結果是系統一啟動就載入了java.lang.String類,就不會再去載入了。

  要點2:判斷一個類是否相等不僅要看類是否名字一樣,而且要看是否有同一個類初始化載入器。所以如果黑客要自己搞一個java.lang.Hack類來載入,由委託模式開始,引導類載入器載入這個類失敗,那就只能交給使用者自定義的類載入起來載入。所以這個類和系統的那個lang包裡的類不在一個初始化載入器裡,就算包名都一樣,還是不能訪問那些包內可見的內容的。

------------------------------------------------------

進一步說明

一,有兩個術語,一個叫“定義類載入器”,一個叫“初始類載入器”。
比如有如下的類載入器結構:
bootstrap
  ExtClassloader
    AppClassloader
    -自定義clsloadr1
    -自定義clsloadr2 
如果用“自定義clsloadr1”載入java.lang.String類,那麼根據雙親委派最終bootstrap會載入此類,那麼bootstrap類就叫做該類的“定義類載入器”,而包括bootstrap的所有得到該類class例項的類載入器都叫做“初始類載入器”。

二,所說的“名稱空間”,是指jvm為每個類載入器維護的一個“表”,這個表記錄了所有以此類載入器為“初始類載入器”(而不是定義類載入器,所以一個類可以存在於很多的名稱空間中)載入的類的列表,所以,題目中的問題就可以解釋了:
CLTest是AppClassloader載入的,String是通過載入CLTest的類載入器也就是AppClassloader進行載入,但最終委派到bootstrap載入的(當然,String類其實早已經被載入過了,這裡只是舉個例子)。所以,對於String類來說,bootstrap是“定義類載入器”,AppClassloader是“初始類載入器”。根據剛才所說,String類在AppClassloader的名稱空間中(同時也在bootstrap,ExtClassloader的名稱空間中,因為bootstrap,ExtClassloader也是String的初始類載入器),所以CLTest可以隨便訪問String類。這樣就可以解釋“處在不同名稱空間的類,不能直接互相訪問”這句話了。

三,一個類,由不同的類載入器例項載入的話,會在方法區產生兩個不同的類,彼此不可見,並且在堆中生成不同Class例項。

四,那麼由不同類載入器例項(比如-自定義clsloadr1,-自定義clsloadr2)所載入的classpath下和ext下的類,也就是由我們自定義的類載入器委派給AppClassloader和ExtClassloader載入的類,在記憶體中是同一個類嗎?
所有繼承ClassLoader並且沒有重寫getSystemClassLoader方法的類載入器,通過getSystemClassLoader方法得到的AppClassloader都是同一個AppClassloader例項,類似單例模式。
在ClassLoader類中getSystemClassLoader方法呼叫私有的initSystemClassLoader方法獲得AppClassloader例項,在initSystemClassLoader中:
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
。。。
scl = l.getClassLoader();
AppClassloader是sun.misc.Launcher類的內部類,Launcher類在new自己的時候生成AppClassloader例項並且放在自己的私有變數loader裡:
loader = AppClassLoader.getAppClassLoader(extclassloader);
值得一提的是sun.misc.Launcher類使用了一種類似單例模式的方法,即既提供了單例模式的介面getLauncher()又把建構函式設成了public的。但是在ClassLoader中是通過單件模式取得的Launcher 例項的,所以我們寫的每個類載入器得到的AppClassloader都是同一個AppClassloader類例項。
這樣的話得到一個結論,就是所有通過正常雙親委派模式的類載入器載入的classpath下的和ext下的所有類在方法區都是同一個類,堆中的Class例項也是同一個。

----------------------------------------

ContextClassLoader

每個執行緒持有一個ContextClassLoader,可以用get,set方法獲取或定義。如果不加指定,就是啟動執行緒那麼類自己的類載入器。如果不是main執行緒,new出來的執行緒的話,就是父執行緒的類載入器。

  為什麼要有這麼一個東西呢,查了一些資料說是,因為為了安全ClassLoader的委託機制不能滿足一些特定需要,這個時候就要用這種方式走後門。比如jdbc,jndi,tomcat等:

  Java 提供了很多服務提供者介面(Service Provider Interface,SPI),允許第三方為這些介面提供實現。常見的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。這些 SPI 的介面由 Java 核心庫來提供,如 JAXP 的 SPI 介面定義包含在 javax.xml.parsers 包中。這些 SPI 的實現程式碼很可能是作為 Java 應用所依賴的 jar 包被包含進來,可以通過類路徑(CLASSPATH)來找到。而問題在於,SPI 的介面是 Java 核心庫的一部分,是由引導類載入器來載入的;SPI 實現的 Java 類一般是由系統類載入器來載入的。引導類載入器是無法找到 SPI 的實現類的,因為它只加載 Java 的核心庫。它也不能代理給系統類載入器,因為它是系統類載入器的祖先類載入器。也就是說,類載入器的代理模式無法解決這個問題。

  執行緒上下文類載入器正好解決了這個問題。在 SPI 介面的程式碼中使用執行緒上下文類載入器,就可以成功的載入到 SPI 實現的類。執行緒上下文類載入器在很多 SPI 的實現中都會用到。