【Java虛擬機器10】ClassLoader.getSystemClassLoader()流程簡析
前言
學習類載入必然離開不了sun.misc.Launcher這個類和Class.forName()這個方法。
分析ClassLoader.getSystemClassLoader()這個流程可以明白下面幾個知識點:
-
sun.misc.Launcher的初始化
-
初次接觸執行緒上下文類載入器(Thread context class loader)
-
三個引數的Class.forName(String name, boolean initialize, ClassLoader loader)方法
-
怎樣修改JVM預設的系統類載入器
-
Launcher類中存在兩個很重要的內部類:AppClassLoader和ExtClassLoader
-
Launcher類主要負責AppClassLoader的初始化/ExtClassLoader的初始化/執行緒上下文類載入器的初始化
-
Class.forName()是JDK提供給我們用於載入一個Class檔案的方法
第一步:ClassLoader.getSystemClassLoader()
當客戶端希望獲取系統類載入器的時候,需要第一次呼叫ClassLoader.getSystemClassLoader()
靜態方法,該方法第一步即會去嘗試獲取一個sun.misc.Launcher
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
第二步:Launcher例項的初始化
這裡的例項初始化是不區分方法的,這是Launcher類的靜態變數的初始化,通過之前學習類載入的知識可以知道:類初始化後,類的靜態變數會初始化。所以下面的程式碼得到呼叫:
private static Launcher launcher = new Launcher();
下面我們看一下Launcher的無參構造器:
public class Launcher { public Launcher() { Launcher.ExtClassLoader var1; try { /** * 呼叫靜態內部類的靜態方法:獲取ExtClassLoader(第一次呼叫時會初始化) * 這裡的getExtClassLoader()方法使用了DCL模式建立一個ExtClassLoader的單例。 * 初始化的時候,內部包含ExtClassLoader的載入路徑:java.ext.dirs */ var1 = Launcher.ExtClassLoader.getExtClassLoader(); } catch (IOException var10) { throw new InternalError("Could not create extension class loader", var10); } try { /** * 呼叫靜態內部類的靜態方法:獲取AppClassLoader(第一次呼叫時會初始化) * 這裡的getAppClassLoader()方法沒有什麼特殊的,就是包含AppClassLoader的載入路徑:java.class.path */ 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 { //SecurityManager預設是被AppClassLoader載入的 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); } } }
第三步:ClassLoader.initSystemClassLoader()
說回ClassLoader.getSystemClassLoader()方法,初始化完成Launcher例項之後,下面就是初始化ClassLoader類中的SystemClassLoader了。
private static synchronized void initSystemClassLoader() {
//如果systemClassLoader沒有被設定
if (!sclSet) {
if (scl != null)
throw new IllegalStateException("recursive invocation");
//獲取Launcher
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
//獲取Launcher的AppClassLoader
scl = l.getClassLoader();
try {
//使用SystemClassLoaderAction類暴露修改SystemClassLoader的功能給User
scl = AccessController.doPrivileged(
new SystemClassLoaderAction(scl));
} catch (PrivilegedActionException pae) {
oops = pae.getCause();
if (oops instanceof InvocationTargetException) {
oops = oops.getCause();
}
}
if (oops != null) {
if (oops instanceof Error) {
throw (Error) oops;
} else {
// wrap the exception
throw new Error(oops);
}
}
}
sclSet = true;
}
}
第四步:利用SystemClassLoaderAction修改AppClassLoader
SystemClassLoaderAction類不是內部類,它是ClassLoader平級的類,但是不是public的。
class SystemClassLoaderAction
implements PrivilegedExceptionAction<ClassLoader> {
private ClassLoader parent;
SystemClassLoaderAction(ClassLoader parent) {
this.parent = parent;
}
public ClassLoader run() throws Exception {
//獲取系統屬性java.system.class.loader
String cls = System.getProperty("java.system.class.loader");
if (cls == null) {
//系統屬性java.system.class.loader為空,返回預設的AppClassLoader【這種情況為預設情況】
return parent;
}
/**
* 若系統屬性java.system.class.loader(設為X)不為空,反射獲取x的class(X為二進位制名字)
* 然後把X對應的類載入器反射初始化,設定為系統類載入器。
* 並將X設定為執行緒上下文類載入器。
* 注意這裡的Class.forName(String name, boolean initialize, ClassLoader loader)方法
*/
Constructor<?> ctor = Class.forName(cls, true, parent)
.getDeclaredConstructor(new Class<?>[] { ClassLoader.class });
ClassLoader sys = (ClassLoader) ctor.newInstance(
new Object[] { parent });
Thread.currentThread().setContextClassLoader(sys);
return sys;
}
}
第五步:三個引數的Class.forName(String name, boolean initialize, ClassLoader loader)方法
- 引數1:類的全名
- 引數2:true表示初始化這個class
- 引數3:loader表示希望用哪個類載入器來載入該類
- 返回 :代表這個名字的類的Class物件
Class.forName(String name, boolean initialize, ClassLoader loader)方法的部分JavaDoc文件:
* @param name fully qualified name of the desired class
* @param initialize if {@code true} the class will be initialized.
* See Section 12.4 of <em>The Java Language Specification</em>.
* @param loader class loader from which the class must be loaded
* @return class object representing the desired class
附
sun.misc.Launcher中的內部類
AppClassLoader
可以看到Launcher類中存在一個靜態內部類AppClassLoader,其中包含如下方法,所以這就是該問題的答案:為何應用類載入器是從java.class.path路徑中載入類?
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
final String var1 = System.getProperty("java.class.path");
final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
public Launcher.AppClassLoader run() {
URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
return new Launcher.AppClassLoader(var1x, var0);
}
});
}
ExtClassLoader
Launcher類中還存在一個靜態內部類ExtClassLoader,其中包含如下方法,在首次建立擴充套件類載入器的時候被呼叫,所以這就是該問題的答案:為何擴充套件類載入器是從java.ext.dirs路徑中載入類?
private static File[] getExtDirs() {
String var0 = System.getProperty("java.ext.dirs");
File[] var1;
if (var0 != null) {
StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
int var3 = var2.countTokens();
var1 = new File[var3];
for(int var4 = 0; var4 < var3; ++var4) {
var1[var4] = new File(var2.nextToken());
}
} else {
var1 = new File[0];
}
return var1;
}