類加載運作全過程
當我們使用Java指令運作某個類時,會先通過類加載器加載類檔案到jvm。大緻流程如下:
① 建立引導類加載器
② C++建立JVM啟動器,執行個體化一個Launcher對象(單例)
③ Launcher對象效用getLauncher方法建立其他的類加載器,先執行個體化擷取一個ExtClassLoader(擴充類加載器)、再擷取AppClassLoader。
JVM預設使用Launcher的getClassLoader()方法傳回的類加載器AppClassLoader的執行個體,調用AppClassLoader的loadClass方法加載我們的應用程式。
類加載流程
執行個體化的Launcher源碼如下:
首先通過C++建立JVM啟動器,并加載Launcher,在加載Launcher時,會首先初始化靜态的對象 private static Launcher launcher = new Launcher();
//初始化Launcher對象
public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();//初始化擴充類加載器ExtClassLoader
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
//初始化應用程式加載器AppClassLoader,并設定其父加載器為ExtClassLoader
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
//設定目前線程的類加載器為應用程式加載器AppClassLoader
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
} catch (InstantiationException var6) {
} catch (ClassNotFoundException var7) {
} catch (ClassCastException var8) {
}
} else {
var3 = new SecurityManager();
}
if (var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
//調用AppClassLoader的loadClass方法加載我們的應用程式,最終會調用ClassLoader的loadClass
//這一步其實就是類加載器和雙親委派機制,參考本篇文章下面章節的描述
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 判斷class是否被加載過
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);//找父 類加載器加載class,加載到則不為null
} else {
c = findBootstrapClassOrNull(name);//父類為null表示為引導類加載器,因為引導類加載器是C++建立的在java中就是null
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
// 如果到引導類加載器仍沒有加載到class,則再依次反轉查找,
//即再由擴充類加載器ExtClassLoader加載,ExtClassLoader再加載不到就由應用程式加載器AppClassLoader加載,總會被加載到的
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);//自己寫的類最後還是通過AppClassLoader的findClass找到(其實是父類的函數),AppClassLoader的父類是URLClassLoader
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
類加載器loadClass方法的類加載過程
步驟:加載 >> 驗證 >> 準備 >> 解析 >> 初始化 >> 使用 >> 解除安裝
加載:在硬碟上查找并通過IO讀入位元組碼檔案,使用到類時才會加載,例如調用類的main()方法,new對象等等,在加載階段會在記憶體中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種資料的通路入口。
驗證:校驗位元組碼檔案的正确性。
準備:給類的靜态變量配置設定記憶體,并賦予預設值。
解析:将符号引用替換為直接引用,該階段會把一些靜态方法(符号引用,比如main()方法)替換為指向資料所存記憶體的指針或句柄等(直接引用),這是所謂的靜态連結過程(類加載期間完成),動态連結是在程式運作期間完成的将符号引用替換為直接引用。
初始化:對類的靜态變量初始化為指定的值,執行靜态代碼塊。
loadclass過程
類加載器及雙親委派機制
Java裡有如下幾種類加載器:
引導類加載器:負責加載支撐JVM運作的位于JRE的lib目錄下的核心類庫,比如rt.jar、charsets.jar等
擴充類加載器:負責加載支撐JVM運作的位于JRE的lib目錄下的ext擴充目錄中的JAR類包。
應用程式類加載器:負責加載ClassPath路徑下的類包,主要就是加載你自己寫的那些類。
自定義加載器:負責加載使用者自定義路徑下的類包。
類加載器雙親委派機制
雙親委派機制:Java類先由父類加載器加載,不行再由子類加載器加載。
打破雙親委派機制方式:自定義類加載器(重寫findClass方法),并重寫loadClass方法。
為什麼要設計雙親委派機制?
沙箱安全機制:自己寫的java.lang.String.class類不會被加載,這樣便可以防止核心API庫被随意篡改。
避免類的重複加載:當父親已經加載了該類時,就沒有必要子ClassLoader再加載一次,保證被加載類的唯一性。
Tomcat自定義加載器就是打破雙親委派機制,自定義類加載器。
備注
本文部分文案來自于圖靈課堂講義。