算上大學,盡管接觸Java已經有4年時間并對基本的API算得上熟練應用,但是依舊覺得自己對于Java的特性依然是一知半解。要成為優秀的Java開發人員,需要深入了解Java平台的工作方式,其中類加載機制和JVM位元組碼這樣的核心特性。今天我将記錄一下我在新的學習路程中對Java類加載機制的了解。
1.類加載機制
類加載是一個将類合并到正在運作着的JVM程序中的過程。首先要加載一個類,我們必須先得将類檔案加載進來并連接配接,并且要加上大量的驗證,随後會生成一個代表着這個類的class對象,然後就可以通過它去建立新的執行個體了。這就是我所了解的Java的類加載機制。
經過加載和連接配接後出來的class對象,說明這個類已經被加載到了JVM中,此後就不會再加載了。
2.類加載器和雙親委派模型
Java平台裡有幾個經典的類加載器,它們分别在平台的啟動和正常操作中承擔不同的任務:
- 根類加載器(Bootstrap ClassLoader)——通常在虛拟機啟動不久後執行個體化,我們可以将其視作JVM的一部分,他的作用通常是負責加載系統的基礎jar包(主要是rt.jar),并且它不做驗證工作。我們開發者無法直接使用該加載器
- 擴充類加載器(Extension ClassLoader)——用來加載安裝時自帶的标準擴充。一般包括安全性擴充。我們開發者可以直接使用。
- 應用(或系統)類加載器(System ClassLoader)——這是應用最廣泛的類加載器。它負責加載應用類。
- 定制類加載器(Custom ClassLoader)——在更複雜的環境中,我們開發者可以通過繼承java.lang.ClassLoader
類的方式實作自己的類加載器以滿足一些特殊的需求。比如說Spring架構裡的封裝了自己的類加載器(我會在随後的spring源碼學習中遇到的吧)
-
線程上下文類加載器(Context ClassLoader)——預設為系統類加載器,可通過Thread.currentThread().setContextClassLoader(ClassLoader)來設定,每個線程都可以将線程上下文類加載器預先設定為父線程的類加載器。這個主要用于打破雙親委派模型,容許父類加載器通過子類加載器加載所需要的類庫。
說到雙親委派模型,我們可以通過這個圖可知:
如果說一個類加載器收到類加載請求,它并不會馬上去找,它會先把這個請求委托給他的父類加載器去完成,隻有它的父類加載器回報說找不到了,它才會自己去找(注:父類加載器它們的預設的目錄路徑都是不一樣的,一個類在虛拟機裡面是用它的全限定類名+它的類加載器來确立它的唯一性),采用雙親委派的好處可以保證系統中的類不混亂,如你自己寫了一個java.lang.object類,并且路徑也放在lib下面,此時編譯後會有兩個object類了,采用雙親委派就隻會加載rt.jar裡面的object而不加載你自己寫的。
3.加載類
手動加載類有兩種方式,Class.forName()和ClassLoader.loadClass()兩種:
我們從源碼來看看他們的差別
Class.forName()
它有兩個重載方法
<pre name="code" class="java"> public static Class<?> forName(String className)
throws ClassNotFoundException {
return forName0(className, true, ClassLoader.getCallerClassLoader());
}
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
{
if (loader == null) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader ccl = ClassLoader.getCallerClassLoader();
if (ccl != null) {
sm.checkPermission(
SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
}
}
return forName0(name, initialize, loader);
}
/** Called after security checks have been made. */
private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException;
第一個方法預設初始化類。
第二個方法可以選擇是否初始化類和可以選擇類加載器。
它們倆最終都是傳回forName0,而forName0有native關鍵字,原生态的方法,是其他語言實作的,我就不深究下去了。
ClassLoader.loadClass()
它同樣也有兩個重載方法
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
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;
}
}
第一個方法的具體實作是第二個方法。 resolve 參數為false。
第二個方法先檢查這個類是否被加載過,如果沒有被加載過,那就通過雙親委派的模式去找,如果父類加載器不是根類加載器,那就遞歸調用,如果是,那就試着傳回根類加載器所加載的類或者傳回null,當虛拟機想要裝入的不僅包括指定的類似,resolve參數為true,裝入該類英語的所有其他類。這個方法還用了其他的類,它們基本都是用native修飾的,在這也不深究,知道是用來幹嘛的就好了。
以上就是我所學習的關于Java類加載器的内容。