JAVA類裝載方式,有兩種:java
1.隱式裝載, 程序在運行過程當中當碰到經過new 等方式生成對象時,隱式調用類裝載器加載對應的類到jvm中。 2.顯式裝載, 經過class.forname()等方法,顯式加載須要的類算法
類加載的動態性體現:apache
一個應用程序老是由n多個類組成,Java程序啓動時,並非一次把全部的類所有加載後再運行,它老是先把保證程序運行的基礎類一次性加載到jvm中,其它類等到jvm用到的時候再加載,這樣的好處是節省了內存的開銷,由於java最先就是爲嵌入式系統而設計的,內存寶貴,這是一種能夠理解的機制,而用到時再加載這也是java動態性的一種體現api
java類裝載器數組
JDK 默認提供了以下幾種ClassLoader安全
Bootstrp loader
Bootstrp加載器是用C++語言寫的,它是在Java虛擬機啓動後初始化的,它主要負責加載%JAVA_HOME%/jre/lib
,-Xbootclasspath
參數指定的路徑以及%JAVA_HOME%/jre/classes
中的類。網絡
ExtClassLoader
Bootstrp loader加載ExtClassLoader,而且將ExtClassLoader的父加載器設置爲Bootstrp loader.ExtClassLoader是用Java寫的,具體來講就是 sun.misc.Launcher$ExtClassLoader,ExtClassLoader主要加載%JAVA_HOME%/jre/lib/ext
,此路徑下的全部classes目錄以及java.ext.dirs
系統變量指定的路徑中類庫。jvm
AppClassLoader
Bootstrp loader加載完ExtClassLoader後,就會加載AppClassLoader,而且將AppClassLoader的父加載器指定爲 ExtClassLoader。AppClassLoader也是用Java寫成的,它的實現類是 sun.misc.Launcher$AppClassLoader,另外咱們知道ClassLoader中有個getSystemClassLoader
方法,此方法返回的正是AppclassLoader.AppClassLoader主要負責加載classpath所指定的位置的類或者是jar文檔,它也是Java程序默認的類加載器。ide
綜上所述,它們之間的關係能夠經過下圖形象的描述:ui
爲何要有三個類加載器,一方面是分工,各自負責各自的區塊,另外一方面爲了實現委託模型。
類加載器之間是如何協調工做的
前面說了,java中有三個類加載器,問題就來了,碰到一個類須要加載時,它們之間是如何協調工做的,即java是如何區分一個類該由哪一個類加載器來完成呢。 在這裏java採用了委託模型機制,這個機制簡單來說,就是「類裝載器有載入類的需求時,會先請示其Parent使用其搜索路徑幫忙載入,若是Parent 找不到,那麼才由本身依照本身的搜索路徑搜索類」
下面舉一個例子來講明,爲了更好的理解,先弄清楚幾行代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
Public class Test{
Public static void main(String[] arg){
ClassLoader c = Test.class.getClassLoader(); //獲取Test類的類加載器
System.out.println(c);
ClassLoader c1 = c.getParent(); //獲取c這個類加載器的父類加載器
System.out.println(c1);
ClassLoader c2 = c1.getParent();//獲取c1這個類加載器的父類加載器
System.out.println(c2);
}
}
|
運行結果:
1
2
3
4
5
|
……AppClassLoader……
……ExtClassLoader……
Null
|
能夠看出Test是由AppClassLoader加載器加載的,AppClassLoader的Parent
加載器是 ExtClassLoader,可是ExtClassLoader
的Parent
爲 null
是怎麼回事呵,朋友們留意的話,前面有提到Bootstrap Loader是用C++語言寫的,依java的觀點來看,邏輯上並不存在Bootstrap Loader的類實體,因此在java
程序代碼裏試圖打印出其內容時,咱們就會看到輸出爲null
。
類裝載器就是尋找類或接口字節碼文件進行解析並構造JVM內部對象表示的組件,在java中類裝載器把一個類裝入JVM,通過如下步驟:
一、裝載:查找和導入Class文件 二、連接:其中解析步驟是能夠選擇的 (a)檢查:檢查載入的class文件數據的正確性 (b)準備:給類的靜態變量分配存儲空間 (c)解析:將符號引用轉成直接引用 三、初始化:對靜態變量,靜態代碼塊執行初始化工做
類裝載工做由ClassLoder
和其子類負責。JVM在運行時會產生三個ClassLoader:根裝載器,ExtClassLoader
(擴展類裝載器)和AppClassLoader
,其中根裝載器不是ClassLoader的子類,由C++編寫,所以在java中看不到他,負責裝載JRE的核心類庫,如JRE目錄下的rt.jar,charsets.jar等。ExtClassLoader
是ClassLoder
的子類,負責裝載JRE擴展目錄ext下的jar類包;AppClassLoader
負責裝載classpath路徑下的類包,這三個類裝載器存在父子層級關係****,即根裝載器是ExtClassLoader的父裝載器,ExtClassLoader是AppClassLoader的父裝載器。默認狀況下使用AppClassLoader裝載應用程序的類
Java裝載類使用「全盤負責委託機制」。「全盤負責」是指當一個ClassLoder
裝載一個類時,除非顯示的使用另一個ClassLoder
,該類所依賴及引用的類也由這個ClassLoder
載入;「委託機制」是指先委託父類裝載器尋找目標類,只有在找不到的狀況下才從本身的類路徑中查找並裝載目標類。這一點是從安全方面考慮的,試想若是一我的寫了一個惡意的基礎類(如java.lang.String
)並加載到JVM
將會引發嚴重的後果,但有了全盤負責制,java.lang.String
永遠是由根裝載器來裝載,避免以上狀況發生 除了JVM默認的三個ClassLoder
之外,第三方能夠編寫本身的類裝載器,以實現一些特殊的需求。類文件被裝載解析後,在JVM
中都有一個對應的java.lang.Class
對象,提供了類結構信息的描述。數組,枚舉及基本數據類型,甚至void
都擁有對應的Class
對象。Class
類沒有public
的構造方法,Class
對象是在裝載類時由JVM
經過調用類裝載器中的defineClass()
方法自動構造的。
線程上下文類加載器(context class loader)是從 JDK 1.2 開始引入的。類 java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用來獲取和設置線程的上下文類加載器。若是沒有經過 setContextClassLoader(ClassLoader cl)方法進行設置的話,線程將繼承其父線程的上下文類加載器。Java 應用運行的初始線程的上下文類加載器是系統類加載器。在線程中運行的代碼能夠經過此類加載器來加載類和資源。
前面提到的類加載器的代理模式並不能解決 Java 應用開發中會遇到的類加載器的所有問題。Java 提供了不少服務提供者接口(Service Provider Interface,SPI),容許第三方爲這些接口提供實現。常見的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。這些 SPI 的接口由 Java 核心庫來提供,如 JAXP 的 SPI 接口定義包含在 javax.xml.parsers包中。這些 SPI 的實現代碼極可能是做爲 Java 應用所依賴的 jar 包被包含進來,能夠經過類路徑(CLASSPATH)來找到,如實現了 JAXP SPI 的 Apache Xerces所包含的 jar 包。SPI 接口中的代碼常常須要加載具體的實現類。如 JAXP 中的 javax.xml.parsers.DocumentBuilderFactory類中的 newInstance()方法用來生成一個新的 DocumentBuilderFactory的實例。這裏的實例的真正的類是繼承自 javax.xml.parsers.DocumentBuilderFactory,由 SPI 的實現所提供的。如在 Apache Xerces 中,實現的類是 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl。而問題在於,SPI 的接口是 Java 核心庫的一部分,是由引導類加載器來加載的;SPI 實現的 Java 類通常是由系統類加載器來加載的。引導類加載器是沒法找到 SPI 的實現類的,由於它只加載 Java 的核心庫。它也不能代理給系統類加載器,由於它是系統類加載器的祖先類加載器。也就是說,類加載器的代理模式沒法解決這個問題。線程上下文類加載器正好解決了這個問題。若是不作任何的設置,Java 應用的線程的上下文類加載器默認就是系統上下文類加載器。在 SPI 接口的代碼中使用線程上下文類加載器,就能夠成功的加載到 SPI 實現的類。線程上下文類加載器在不少 SPI 的實現中都會用到。
對於運行在 Java EE容器中的 Web 應用來講,類加載器的實現方式與通常的 Java 應用有所不一樣。不一樣的 Web 容器的實現方式也會有所不一樣。以 Apache Tomcat 來講,每一個 Web 應用都有一個對應的類加載器實例。該類加載器也使用代理模式,所不一樣的是它是首先嚐試去加載某個類,若是找不到再代理給父類加載器。這與通常類加載器的順序是相反的。這是 Java Servlet 規範中的推薦作法,其目的是使得 Web 應用本身的類的優先級高於 Web 容器提供的類。這種代理模式的一個例外是:Java 核心庫的類是不在查找範圍以內的。這也是爲了保證 Java 核心庫的類型安全。
絕大多數狀況下,Web 應用的開發人員不須要考慮與類加載器相關的細節。下面給出幾條簡單的原則:
(1)每一個 Web 應用本身的 Java 類文件和使用的庫的 jar 包,分別放在 WEB-INF/classes和 WEB-INF/lib目錄下面。
(2)多個應用共享的 Java 類文件和 jar 包,分別放在 Web 容器指定的由全部 Web 應用共享的目錄下面。
(3)當出現找不到類的錯誤時,檢查當前類的類加載器和當前線程的上下文類加載器是否正確。
OSGi是 Java 上的動態模塊系統。它爲開發人員提供了面向服務和基於組件的運行環境,並提供標準的方式用來管理軟件的生命週期。OSGi 已經被實現和部署在不少產品上,在開源社區也獲得了普遍的支持。Eclipse就是基於OSGi 技術來構建的。
OSGi 中的每一個模塊(bundle)都包含 Java 包和類。模塊能夠聲明它所依賴的須要導入(import)的其它模塊的 Java 包和類(經過 Import-Package),也能夠聲明導出(export)本身的包和類,供其它模塊使用(經過 Export-Package)。也就是說須要可以隱藏和共享一個模塊中的某些 Java 包和類。這是經過 OSGi 特有的類加載器機制來實現的。OSGi 中的每一個模塊都有對應的一個類加載器。它負責加載模塊本身包含的 Java 包和類。當它須要加載 Java 核心庫的類時(以 java開頭的包和類),它會代理給父類加載器(一般是啓動類加載器)來完成。當它須要加載所導入的 Java 類時,它會代理給導出此 Java 類的模塊來完成加載。模塊也能夠顯式的聲明某些 Java 包和類,必須由父類加載器來加載。只須要設置系統屬性 org.osgi.framework.bootdelegation的值便可。
假設有兩個模塊 bundleA 和 bundleB,它們都有本身對應的類加載器 classLoaderA 和 classLoaderB。在 bundleA 中包含類 com.bundleA.Sample,而且該類被聲明爲導出的,也就是說能夠被其它模塊所使用的。bundleB 聲明瞭導入 bundleA 提供的類 com.bundleA.Sample,幷包含一個類 com.bundleB.NewSample繼承自 com.bundleA.Sample。在 bundleB 啓動的時候,其類加載器 classLoaderB 須要加載類 com.bundleB.NewSample,進而須要加載類 com.bundleA.Sample。因爲 bundleB 聲明瞭類 com.bundleA.Sample是導入的,classLoaderB 把加載類 com.bundleA.Sample的工做代理給導出該類的 bundleA 的類加載器 classLoaderA。classLoaderA 在其模塊內部查找類 com.bundleA.Sample並定義它,所獲得的類 com.bundleA.Sample實例就能夠被全部聲明導入了此類的模塊使用。對於以 java開頭的類,都是由父類加載器來加載的。若是聲明瞭系統屬性 org.osgi.framework.bootdelegation=com.example.core.*,那麼對於包 com.example.core中的類,都是由父類加載器來完成的。
OSGi 模塊的這種類加載器結構,使得一個類的不一樣版本能夠共存在 Java 虛擬機中,帶來了很大的靈活性。不過它的這種不一樣,也會給開發人員帶來一些麻煩,尤爲當模塊須要使用第三方提供的庫的時候。下面提供幾條比較好的建議: (1)若是一個類庫只有一個模塊使用,把該類庫的 jar 包放在模塊中,在 Bundle-ClassPath中指明便可。 (2)若是一個類庫被多個模塊共用,能夠爲這個類庫單獨的建立一個模塊,把其它模塊須要用到的 Java 包聲明爲導出的。其它模塊聲明導入這些類。 (3)若是類庫提供了 SPI 接口,而且利用線程上下文類加載器來加載 SPI 實現的 Java 類,有可能會找不到 Java 類。若是出現了 NoClassDefFoundError異常,首先檢查當前線程的上下文類加載器是否正確。經過 Thread.currentThread().getContextClassLoader()就能夠獲得該類加載器。該類加載器應該是該模塊對應的類加載器。若是不是的話,能夠首先經過 class.getClassLoader()來獲得模塊對應的類加載器,再經過 Thread.currentThread().setContextClassLoader()來設置當前線程的上下文類加載器。