眾所周知,一個.java文件中所寫的代碼,需要經由編譯器(Javac)進行編譯,獲得.class二進制字節碼文件。而后在運行期間,JVM將使用到的類從.class文件中加載到內存成為java.lang.Class類的一個實例,就可以被JVM進行使用了。
對我這樣的小白來說,僅憑大學時候學的編譯原理還不足以說清其中奧秘。今天想說的是關于類的加載過程,不同(何謂不同?)的類是通過何種方式進行加載的。
0. 不同的類
先拋出一個問題,何謂不同的類?分類方式有多種,既然說加載,就從加載的角度區分。
第一類:Java基礎類庫(rt.jar)
在$JAVA_HOME/jre/lib目錄下存放著rt.jar包,如果用壓縮軟件打開便能看到其中保存著許多java語言的基礎類。
第二類:Java擴展類庫(ext庫)
在$JAVA_HOME/jre/lib/ext目錄下 ,可以看到有很多jar包,他們是作為Java的擴展類庫存放起來的。
第三類:自建類
自建類,即是我們在編輯器中自己寫的.java文件對應的類。
第四類:其他類
這個類型和上面就有一些不一樣了,我們暫時將非上述三種類作為其他類。可能是存放在一個本地目錄中jar包內部的類,也可能是存放在一個遠端服務器中的類,等等。
以上便是從類加載的角度,先對類進行了一定的區分。
1. 不同的類加載器(ClassLoader)
一個Java類需要通過類加載器進行加載動作后,才能被后續的代碼使用。
其實根據上面對類的分類,大家應該能猜到類加載器也會是四種:
引導加載器Bootstrap ClassLoader
擴展加載器Extension Class Loader
系統應用加載器APP Class Loader
自定義加載器Custom Class Loader
引導加載器Bootstrap ClassLoader
什么是Bootstrap?
Tall boots may have a tab, loop or handle at the top known as a bootstrap, allowing one to use fingers or a boot hook tool to help pulling the boots on.
--- From Wikipedia
我在很多地方見過這個單詞,字面意思就是“靴靽”、“拔靴帶”。下圖中靴子上面的有黃色字樣的小帶子即是bootstrap。
那在計算機領域Bootstrap到底意味著什么呢?
In general, bootstrapping usually refers to a self-starting process that is supposed to proceed without external input. In computer technology the term usually refers to the process of loading the basic software into the memory of a computer after power-on or general reset, especially the operating system which will then take care of loading other software as needed.
--- From Wikipedia
說白了就是世界的拓荒者,混沌初開的始祖元神。那么這種類加載器必然是需要加載Java基礎類庫中的類了。
BootstrapClassLoader是用C/C++語言編寫的,JVM會通過特殊方式對其先進行加載,而后通過該加載器進行其他基礎類的加載。它也可用于加載-Xbootclasspath參數指定的路徑中的類。
擴展加載器
由BootstrapClassLoader喚起來加載Java擴展類庫中的類。還可以加載-D java.ext.dirs選項指定的目錄。
系統應用加載器
加載當前項目中$CLASSPATH目錄下的類
自定義加載器
用戶可以對ClassLoader進行extends,而后實現自己定制的ClassLoader。
2. 類加載器的關系-雙親委派模型
先上一張老圖(from http://blog.csdn.net/gjanyanlig/article/details/6818655/):
按照上圖的順序,一層層地進行父親(parent)的設置,即Custom ClassLoader的parent是App ClassLoader,以此類推。
注意:此處其實是進行兒子類中的變量(parent)的設置,而不是類的繼承關系。所以只能稱其為“父親類”和“兒子類”,而不能稱為“父類”和“子類”。
而上面的圖其實表達的就是ClassLoader在進行類加載時的源碼:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 先檢查該類是否已經被加載
// 此方法的實現已附在后面
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// 調用父親的loadClass方法
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
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
protected final Class<?> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
}
private final native Class<?> findLoadedClass0(String name);
可以發現這段代碼的要點為:
- 由子至父進行查找當前類是否已經被加載,即遞歸調用父親的loadClass方法。
- 當某個類加載器中已找到對應類,則停止進行find。
- 如果一直未找到對應類,遞歸調用會直到某個加載器類的parent為null停止,這也表示Bootstrap ClassLoader的parent其實是為null的。
- 由于是遞歸調用,所以當一直為找到對應類時,首先嘗試進行類加載的是Bootstrap,調用的主要方法即是findClass函數。
- 如果父親類沒有完成加載的話,才會一層層讓兒子類去嘗試加載,即自父至子的嘗試加載類。
關于Java類加載的機制其實有很多靈活的利用方式,可以參見文章http://blog.csdn.net/briblue/article/details/54973413 ,我這里便不再進行車輪的重造。
關于Java 9
另外想提一點的是Java 9的問世,對以上知識點進行了一些改動:
將ClassLoader分為三個級別:
- Bootstrap Loader具有最高優先級和權限,主要是核心的系統類;
- Platform Loader用于擴展的一些系統類,例如SQL,XML等;
- Application Loader主要用于應用程序的Loader。
在這三個級別的Loader下面有一個統一Module 管理,用于控制和管理模塊間的依賴關系,可讀性,可訪問性等。可以查看BuildinClassLoader進行深入的學習。另外推薦一篇簡書的文章:http://www.lxweimin.com/p/b133abd54d27, 敘述了很多Java 9的新特性,值得關注。
現階段我也在一點點地學習當中,把所學寫下來,這一過程也讓我對很多細節有了更深刻的認識。繼續努力,堅持下去!