1、Java中的class加載機制有如下三個特性:java
一、全盤負責制 bootstrap
「全盤負責」是指當一個ClassLoader裝載一個類時,除非顯示地使用另外一個ClassLoader,則該類所依賴及引用的類也由這個CladdLoader載入。緩存
例如,系統類加載器AppClassLoader加載入口類(含有main方法的類)時,會把main方法所依賴的類及引用的類也載入,依此類推。「全盤負責」機制也可稱爲當前類加載器負責機制。顯然,入口類所依賴的類及引用的類的當前類加載器就是入口類的類加載器。安全
二、雙親委派制(Parent Delegation)app
1) 委託機制的意義ide
主要是出於安全性考慮,確保Java的核心類在內存中只有一份字節碼,好比兩個類A和類B都要加載java.lang.System類,經過雙親委派,系統只會加載一次java.lang.System,即便用戶重寫了java.lang.System,也不會有機會被加載到,除非你重寫ClassLoader。不過有時候爲了作容器隔離,須要在JVM中對同一個Class有多份字節碼,例如OSGI和Pandora技術,後面會詳細談到。this
2) 委託機制是必須的嗎?spa
「雙親委派」機制只是Java推薦的機制,並非強制的機制。咱們能夠繼承java.lang.ClassLoader類,實現本身的類加載器。若是想保持雙親委派模型,就應該重寫findClass(name)方法;若是想破壞雙親委派模型,能夠重寫loadClass(name)方法。使用本身的類加載器,有不少高級玩法,例如OSGI和Pandora的隔離機制,就是經過自定義ClassLoader來實現的。線程
3) 如何實現雙親委派?3d
默認的ClassLoader的loadClass()實現方式是雙親委派模型,咱們能夠繼承ClassLoader去自定義本身的ClassLoader,若是不重寫loadClass方法,那麼默認也是雙親委派的。例如,URLClassLoader只是實現了findClass( ),而loadClass( )仍是繼承ClassLoader的,因此其依然是Parent Delegation的。下面是ClassLoader.loadClass( )源碼,看下雙親委派是怎麼實現的。
1)Check這個class是否被裝載過,若是有直接返回。
裝載過的類是被緩存起來的,這樣確保了同一個類不會被加載兩次,不過有一個問題,用什麼來做爲class緩存的key呢?在JVM中,class是用類全名(包名+類名) 再加上加載這個類的classLoader來惟一標識的,例如class的類全名是C1,classLoader是L1,那麼這個class instance在JVM中的key就是(C1, L1),此時另外一個classLoader L2也加載了該類,那麼將會有另外一個class instance (C1, L2),這兩個class instance是不一樣的type,若是這兩個class的object作賦值操做的話,會出現ClassCastException。
2)嘗試從parent classloader去加載類。
3)若是parent是null(當parent是bootstrap時就是null了),試圖從BootstrapClassLoader的native方法去加載類。
4)在上面嘗試都失敗的狀況下,嘗試本身去加載。
三、按需加載 (On-demand Loading)
何時Class會被JVM加載呢? 回答是隻有當class被用到時,纔會被load,例如new instance,調用其static變量和方法,或使用反射調用其class對象。
這個很容易驗證,在啓動參數里加上-verbose:class, 就能夠清晰看到class是什麼時候被加載的。
2、JVM中classloader加載class的順序
3、ContextClassloader的用處
1)什麼是ContextClassLoader
Thread的一個屬性,能夠在運行時,經過setContextClassLoader方法來指定一個合適的classloader做爲這個線程的contextClassLoader,而後在任何地方經過getContextClassLoader方法來得到此contextClassLoader,用它載入咱們所須要的Class。若是沒有被顯示set過,默認是system classloader。利用這個特性,咱們能夠「打破」classloader委託機制,父classloader能夠得到當前線程的contextClassLoader,而這個contextClassLoader能夠是它的子classloader或者其餘的classloader。
2) 爲何要使用ContextClassLoader
Thread context classloaders provide a back door around the classloading delegation scheme.
Take JNDI for instance: its guts are implemented by bootstrap classes in rt.jar (starting with J2SE 1.3), but these core JNDI classes may load JNDI providers implemented by independent vendors and potentially deployed in the application's -classpath. This scenario calls for a parent classloader (the primordial one in this case) to load a class visible to one of its child classloaders (the system one, for example). Normal J2SE delegation does not work, and the workaround is to make the core JNDI classes use thread context loaders, thus effectively "tunneling" through the classloader hierarchy in the direction opposite to the proper delegation.
這個後門在SPI的實現中頗有用,由於接口類是在parent classloader中加載的,而實現類是由它的child classloader加載的,使用contextClassLoader能夠繞過雙親委派,達到在parent中使用child classloader去load class的目的。其過程以下圖所示:
這樣的示例在JDK中很常見,例如JNDI和JAXP都是經過這樣的方式去加載具體的provider的。例如
javax.xml.ws.spi.FactoryFinder Object find(String factoryId, String fallbackClassName){ ClassLoader classLoader; try { // get context classloader, mostly it's system classloader, but it could be user-defined classloader as well. classLoader = Thread.currentThread().getContextClassLoader(); } catch (Exception x) { throw new WebServiceException(x.toString(), x); } String serviceId = "META-INF/services/" + factoryId; // try to find services in CLASSPATH // Note that if it's not system classloader, this will invoke user-defined classloader's findResource( ) to find services when all its parents failed. try { InputStream is=null; if (classLoader == null) { is=ClassLoader.getSystemResourceAsStream(serviceId); } else { is=classLoader.getResourceAsStream(serviceId); } ..... return newInstance(fallbackClassName, classLoader); }