先簡單介紹下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 的實現中都會用到。
http://www.cnblogs.com/onlywujun/p/3528160.html