JVM 啓動的時候會產生3個ClassLoader,他們分別負責加載不同目錄下的class。其中Bootstrap ClassLoader 是 C++ 實現,如果 parent ClassLoader是它的話,獲取的時候是會返回null 的。其中 System ClassLoader 也就是我們所說的 AppClassLoader。
- Bootstrap ClassLoader/啓動類加載器
主要負責jdk_home/lib目錄下的核心 api 或 -Xbootclasspath 選項指定的jar包裝入工作。 - Extension ClassLoader/擴展類加載器
主要負責jdk_home/lib/ext目錄下的jar包或 -Djava.ext.dirs 指定目錄下的jar包裝入工作。 - System ClassLoader/系統類加載器
主要負責java -classpath/-Djava.class.path所指的目錄下的類與jar包裝入工作。 - User Custom ClassLoader/用戶自定義類加載器(java.lang.ClassLoader的子類)
在程序運行期間, 通過java.lang.ClassLoader的子類動態加載class文件, 體現java動態實時類裝入特性。
關於類加載順序是 採用“雙親委派”加載鏈模式的。用戶自定義的 ClassLoader 是通過繼承 抽象ClassLoader 來實現。
這種加載機制可以避免重複加載並保障java核心類的加載安全性。
而線程上下文加載器 則可以打破這種 “雙親委派”加載鏈模式。
每個線程都有一個關聯的上下文Classloader。如果使用new Thread()方式生成新的線程,新線程將繼承其父線程的上下文Classloader。如果程序對線程上下文Classloader沒有任何改動的話,程序中所有的線程將都使用System Classloader作爲上下文Classloader。
當使用Thread.currentThread().setContextClassLoader(classloader)時,線程上下文 Classloader就變成了指定的Classloader了。此時,在本線程的任意一處地方,調用 Thread.currentThread().getContextClassLoader(),都可以得到前面設置的Classloader。
回到JAXP的例子,假設xercesImpl.jar只有AClassLoader能裝載,現在A.class內部要使用JAXP,但是A.class卻不是由AClassLoader或者它的子Classloader裝載的,那麼在A.class中,應該這樣寫才能正確得到xercesImpl的實現:
AClassLoader aClassLoader = new AClassLoader(parent); Thread.currentThread().setContextClassLoader(aClassLoader); SAXParserFactory factory = SAXParserFactory.getInstance(); ...
JAXP這時就可以通過線程上下文Classloader裝載xercesImpl的實現類了,當然,還有一個前提是在配製文件或啓動參數中指定了使用xerces作爲JAXP的實現。下面是JAXP中的代碼片斷:
ClassLoader cl = Thread.currentThread().getContextClassLoader(); … Class providerClass = cl.loadClass(className);
Java默認的線程上下文類加載器是 系統類加載器(AppClassLoader)。
使用線程上下文類加載器, 可以在執行線程中, 拋棄雙親委派加載鏈模式, 使用線程上下文裏的類加載器加載類.
典型的例子有, 通過線程上下文來加載第三方庫jndi實現, 而不依賴於雙親委派.
大部分java app服務器(jboss, tomcat..)也是採用contextClassLoader來處理web服務。
還有一些採用 hotswap 特性的框架, 也使用了線程上下文類加載器, 比如 seasar (full stack framework in japenese).
http://tomcat.apache.org/tomcat-5.5-doc/class-loader-howto.html 這裏是關於tomcat 容器的類加策略。
http://www.javaworld.com/javaqa/2003-06/01-qa-0606-load.html 這裏是一片關於 java Context Loader的應用說明。
static塊在什麼時候執行?
- 當調用forName(String)載入class時執行,如果調用ClassLoader.loadClass並不會執行.forName(String,false,ClassLoader)時也不會執行.
- 如果載入Class時沒有執行static塊則在第一次實例化時執行.比如new ,Class.newInstance()操作
- static塊僅執行一次
各個java類由哪些classLoader加載?
- java類可以通過實例.getClass.getClassLoader()得知
- 接口由AppClassLoader(System ClassLoader,可以由ClassLoader.getSystemClassLoader()獲得實例)載入
- ClassLoader類由bootstrap loader載入
類裝載的方式
類裝載的方式主要有兩種:顯式的和隱式的。
-
顯式類裝載
發生在使用以下方法調用進行裝載類的時候:
-
ClassLoader.loadClass()(使用指定的Classloader進行裝載)
-
Class.forName()(使用當前類的Caller Classloader進行裝載)
當調用上述方法的時候,指定的Class(以類名爲參數)由Classloader裝入。這兩個方法的行爲有輕微的區別,Class.forName()在類裝載完成後,會對類進行初始化,而ClassLoader.loadClass()只負責裝載類。
-
-
隱式類裝載
發生在由於引用、實例化或繼承導致需要裝載類的時候。隱式類裝載是在幕後啓動的,JVM會解析必要的引用並裝載類。
類的裝載通常組合了顯式和隱式兩種方式。例如,Classloader可能先顯式地裝載一個類,然後再隱式地裝載它引用的其它類。
-
類裝載發生的時間
從類裝載方式的描述中我們可以看到,只有在顯式的調用方法或者實例化、引用、繼承一個類時,類才真正被裝載。由此,我們可以知道,import並不會導致類裝載,以及,在一個類實例化之前,調用它的靜態方法,會導致這個類和它的父類、實現的接口和相關的靜態成員的類會被裝載,而它的成員變量的類卻不會被裝載
父裝載器不能夠引用子裝載器Class,
JVM中類的唯一性
JVM 爲每一個Classloader維護一個唯一標識。在一個JVM裏(對應一個Java進程),可以由不同的Classloader裝載多個同名的類(指包名和類名都完全相同,下同),爲了唯一地標識被不同Classloader裝載的類,JVM會在被裝載的類名前加上裝載該類的Classloader的標識。
摘抄 :
http://blog.chenlb.com/2009/06/java-classloader-architecture.html
http://infocenter.apusic.com/help/index.jsp?topic=/aas/v6/ch27s01.html