對java中classloader使用的一點理解(轉)

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

  Java 中的類加載器大體能夠分紅兩類,一類是系統提供的,另一類則是由 Java 應用開發人員編寫的。系統提供的類加載器主要有下面三個:java

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

  擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫。Java 虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄裏面查找並加載 Java 類。tomcat

  系統類加載器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來加載 Java 類。通常來講,Java 應用的類都是由它來完成加載的。能夠經過 ClassLoader.getSystemClassLoader() 來獲取它。安全

  除了系統提供的類加載器之外,開發人員能夠經過繼承 java.lang.ClassLoader 類的方式實現本身的類加載器,以知足一些特殊的需求。jvm

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

  要點1:爲何採用這種委託方式,是爲了安全,好比用戶自定義了個java.lang.String,那麼若是不交給引導類加載器去加載的話,內存中就會有不止一個String的類實例。並且一個限定包內訪問權限的內容,黑客也能夠用這種方式獲取(要點2再繼續說明)。採用了這種方式的話,引導類加載器只會加載一次類,看見用戶自定義的String來了,就去看本身有沒有加載,結果是系統一啓動就加載了java.lang.String類,就不會再去加載了。函數

  要點2:判斷一個類是否相等不只要看類是否名字同樣,並且要看是否有同一個類初始化加載器。因此若是黑客要本身搞一個java.lang.Hack類來加載,由委託模式開始,引導類加載器加載這個類失敗,那就只能交給用戶自定義的類加載起來加載。因此這個類和系統的那個lang包裏的類不在一個初始化加載器裏,就算包名都同樣,仍是不能訪問那些包內可見的內容的。spa

------------------------------------------------------線程

進一步說明

一,有兩個術語,一個叫「定義類加載器」,一個叫「初始類加載器」。
好比有以下的類加載器結構:
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

相關文章
相關標籤/搜索