不管你是跟同事、同窗、上下級、同行、或者面試官討論技術問題的時候,很容易捲入JVM大型撕逼現場。爲了可以讓你們從大型撕逼現場中脫穎而出,最近我苦思冥想如何把知識點儘量呈現的容易理解,方便記憶。因而就開啓了這一系列文章的編寫。爲了讓JVM相關知識點可以造成一個體系,arthinking將編寫整理一系列的專題,以儘可能以圖片的方式描述相關知識點,而且最終把全部相關知識點串成了一張圖。持續更新中,歡迎你們閱讀。有任何錯落之處也請您高擡貴手幫忙指正,感謝!html
類加載器: 能夠實現經過一個類的全限定名稱來獲取描述此類的二進制字節流。實現這個動做的代碼模塊成爲」類加載器「。java
經過自定義類加載器能夠實現各類有趣而強大的功能更:OSGi,熱部署,代碼加密等。web
如上圖爲類加載器的加載流程。面試
這裏簡單描述下:算法
**啓動類加載器:**系統啓動的時候,首先會經過由C++實現的啓動類加載器,加載<JAVA_HOME>/lib目錄下面的jar包,或者被-Xbootclasspath參數指定的路徑而且被虛擬機識別的文件名的jar包。把相關Class加載到方法區中。spring
這一步會加載關鍵的一個類:sun.misc.Launcher
。這個類包含了兩個靜態內部類:數據庫
能夠反編譯rt.jar文件查看詳細代碼:編程
在加載到Launcher類完成後,會對該類進行初始化,初始化的過程當中,會建立 ExtClassLoader 和 AppClassLoader,源碼以下:bootstrap
public Launcher() {
ExtClassLoader extClassLoader;
try {
extClassLoader = ExtClassLoader.getExtClassLoader();
} catch (IOException iOException) {
throw new InternalError("Could not create extension class loader", iOException);
}
try {
this.loader = AppClassLoader.getAppClassLoader(extClassLoader);
} catch (IOException iOException) {
throw new InternalError("Could not create application class loader", iOException);
}
Thread.currentThread().setContextClassLoader(this.loader);
...
複製代碼
因爲啓動類加載器是由C++實現的,因此在Java代碼裏面是訪問不到啓動類加載器的,若是嘗試經過String.class.getClassLoader()
獲取啓動類的引用,會返回null
;後端
問題:
啓動類加載器,擴展類加載器和應用類加載器都是又誰加載的?
- 啓動類加載器是JVM的內部實現,在JVM申請好內存以後,由JVM建立這個啓動類加載器
- 擴展類加載器和應用程序類加載器是由啓動類加載器加載進來的;
說說如下代碼輸出什麼:
public static void main(String[] args) { System.out.println("加載當前類的加載器:" + TestClassLoader.class.getClassLoader()); System.out.println("加載應用程序類加載器的加載器" + TestClassLoader.class.getClassLoader().getClass().getClassLoader()); System.out.println("String類的啓動類加載器" + String.class.getClassLoader()); } 複製代碼
如上圖,擴展類加載器負責加載<JAVA_HOME>/lib/ext目錄下或者被java.ext.dirs系統變量指定的路徑中的類。
引用程序類加載器加載用戶類路徑下制定的類庫,若是應用程序沒有自定義過本身的類加載器,此類加載器就是默認的類加載器。
引用程序類加載器也叫系統類加載器,能夠經過getSystemClassLoader
方法獲得應用程序類加載器。
注意,如上圖經過以上三個類加載器加載類到方法區以後,方法區中分別對應有各自的類信息存儲區。不一樣類加載器加載的同一個類文件不相等。
雙親委派模型在JDK1.2以後被引入,並普遍使用,這不是一個強制性的約束模型,二貨思Java設計者推薦給開發者的一種類加載器實現方式。咱們也能夠覆蓋對應的方式,實現本身的加載模型。
類加載器的雙親委派機制以下:
也就是說:
問題:
- 爲何要有這麼複雜的雙親委派機制?
- 若是沒有這種機制,咱們就能夠篡改啓動類加載器中須要的類了,如,修本身編寫一個
java.lang.Object
用本身的類加載器進行加載,系統中就會存在多個Object類,這樣Java類型體系最基本的行爲也就沒法保證了。
JDK中默認的雙親委派處理流程是怎麼的呢?接下來咱們看看代碼,如下是java.lang.ClassLoader.loadClass()
方法的實現:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
複製代碼
轉成流程圖,便是:
如山圖因此,老是先回嘗試讓父類加載器先加載,其次判斷啓動類加載器是否已經加載了,最後才嘗試從當前類加載器加載。轉換爲更清晰的模型以下:
雙親委派模型具備如下特色:
啓動類加載器,擴展類加載器,應用程序類加載器,他們分別管理者各自方法區裏的一個區塊。
根據上一篇文章咱們知道,方法區裏面主要存儲的是類的運行時數據結構,這個類的在方法區中的各類數據結構信息經過類的Class實例進行訪問。
以下圖:
方法區裏面存儲着加載進來的類信息,方法區同時僱傭了兩類工種幫忙幹活:
另外,方法區裏面,啓動類加載器類信息對擴展兩類加載器類信息可見,而前面二者的類信息又對應用程序類加載器類信息可見。
在JDK1.0已經存在了ClassLoader類,可是當時尚未雙親委派機制,用戶爲了自定義類加載器,須要從新loadClass()
方法,而咱們知道,在JDK1.2之後,loadClass裏面就是雙親委派機制的實現代碼,此時,要實現自定義類加載器,須要從新findClass()類便可。
若是從新了loadClass()方法,也就意味着再也不遵循雙親委派模型了。
爲何須要這個東西呢,咱們仍是從一個案例來講起。
Tomcat中的類加載器
咱們知道Tomcat目錄結構中有如下目錄:
/common/: 該目錄下的類庫可被Tomcat和全部的WebApp共同使用;
/server/: 該目錄下的類庫可被Tomcat使用,但對全部的WebApp不可見;
/shared/: 該目錄下的類庫可被全部的WebApp共同使用,但對Tomcat本身不可見;
另外Web應用程序還有自身的類庫,放在/WebApp/WEB-INF
目錄中:這裏面的類庫僅僅能夠被此Web應用程序使用,對Tomcat和其餘Web應用程序都不可見。 爲了實現以上各個目錄的類庫可見性效果,Tomat提供了以下的自定義類加載器:
如今以下場景:
咱們發現Tomcat下面有若干個webapp,每一個webapp都用到了spring,因而咱們把spring的jar包放到了shared
目錄中。
因而問題出現了:因爲spring的jar包是由Shared類加載器加載的,假設咱們要使用SpringContext的getBean方法,獲取webapp中的Bean,若是是按照雙親委派模型,就會有問題了,由於webapp中的Java類是對SharedClassLoader不可見的:
Spring中的線程上下文類加載器
爲了解決這個問題,Spring使用了線程上下文類加載器,即從ThreadLocal中獲取到當前線程的上下文類加載器,來加載全部的類庫和類。
關於Spring初始化源碼相關解讀,參考個人這邊文章:Spring IoC原理剖析。
Spring中的bean類加載器
ApplicationContext中有一個beanClassLoader
字段,這個是bean的類加載器,在prepareBeanFactory()方法中作了初始化:
beanFactory.setBeanClassLoader(getClassLoader());
複製代碼
getClassLoader方法以下:
@Override
@Nullable
public ClassLoader getClassLoader() {
return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
}
複製代碼
ClassUtils.getDefaultClassLoader()方法:
@Nullable
public static ClassLoader getDefaultClassLoader() {
ClassLoader cl = null;
try {
cl = Thread.currentThread().getContextClassLoader();
}
catch (Throwable ex) {
// Cannot access thread context ClassLoader - falling back...
}
if (cl == null) {
// No thread context class loader -> use class loader of this class.
cl = ClassUtils.class.getClassLoader();
if (cl == null) {
// getClassLoader() returning null indicates the bootstrap ClassLoader
try {
cl = ClassLoader.getSystemClassLoader();
}
catch (Throwable ex) {
// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
}
}
}
return cl;
}
複製代碼
能夠發現,這裏最終取了當前線程上下文中的ClassLoader。
加載Bean
咱們來看看Spring加載Class的代碼。這裏咱們直接找到實例化Singletons的方法跟進去找須要關注的代碼:
咱們發如今加載Bean Class的時候調用了這個方法:
AbstractBeanFactory:
ClassLoader beanClassLoader = getBeanClassLoader();
複製代碼
也就是用到了ApplicationContext中的beanClassLoader
,線程上下文類加載器來加載Bean Class實例。
總結
Spring做爲一個第三方類庫,可能被任何的ClassLoader加載,因此最靈活的方式是直接使用上下文類加載器。
主要是相似OSGi這類的模塊化熱部署技術。在OSGi中再也不是雙親委派模型中的樹狀結構,而是更復雜的網狀結構。
Where are static methods and static variables stored in Java?
《深刻理解Java虛擬機-JVM高級特性與最佳實踐》
Chapter 5. Loading, Linking, and Initializing
本文爲arthinking
基於相關技術資料和官方文檔撰寫而成,確保內容的準確性,若是你發現了有何錯漏之處,煩請高擡貴手幫忙指正,萬分感激。
你們能夠關注個人博客:itzhai.com
獲取更多文章,我將持續更新後端相關技術,涉及JVM、Java基礎、架構設計、網絡編程、數據結構、數據庫、算法、併發編程、分佈式系統等相關內容。
若是您以爲讀完本文有所收穫的話,能夠關注
個人帳號,或者點贊
的,您的支持就是我寫做的動力!關注個人公衆號,及時獲取最新的文章。
本文做者: arthinking
博客連接: www.itzhai.com/jvm/what-is…
版權聲明:
BY-NC-SA
許可協議:創做不易,如需轉載,請務必附加上博客連接,謝謝!