更多 JVM 文章,欢迎访问个人网站 https://www.zhoutao123.com
Launcher 源码分析
拓展类加载器是 Launcher 类的一个内部类,为了学习应用类加载器和拓展类加载器的实现细节,我们需要对 Launcher 类的源码进行学习。
由于 Launcher 的代码属于 Oracle 闭源的代码部分,这一块代码的分析属于 IDEA 反编译出来的,所以部分变量的命名为 var1, var2 这种奇怪的形式
1、获取系统类加载器
想要获取系统类加载器,可以通过 ClassLoader.getSystemClassLoader() 获取到, 这个方法中实现了对系统类加载的创建,他的说明文档中也阐述了部分细节。
/**
* Returns the system class loader for delegation. This is the default
* delegation parent for new <tt>ClassLoader</tt> instances, and is
* typically the class loader used to start the application.
*
* <p> This method is first invoked early in the runtime's startup
* sequence, at which point it creates the system class loader and sets it
* as the context class loader of the invoking <tt>Thread</tt>.
*
* <p> The default system class loader is an implementation-dependent
* instance of this class.
*
* <p> If the system property "<tt>java.system.class.loader</tt>" is defined
* when this method is first invoked then the value of that property is
* taken to be the name of a class that will be returned as the system
* class loader. The class is loaded using the default system class loader
* and must define a public constructor that takes a single parameter of
* type <tt>ClassLoader</tt> which is used as the delegation parent. An
* instance is then created using this constructor with the default system
* class loader as the parameter. The resulting class loader is defined
* to be the system class loader.
大致翻译一下:
此方法返回系统类加载器,这个加载器是使用 ClassLoader 创建的加载器的默认父加载器,它通常被用来启动应用程序。在程序早期运行的时候,此方法将会被第一次执行。当此方法被执行的时候,程序会创建系统类加载器 & 并将其设置其调用该方法的上下文类加载器。这个系统类加载器是依赖于实现的一个类的实例,(即 AppClassloader 是抽象类 ClassLoader 类的一个实现,系统类加载器是这个实现类的一个实例)。
如果系统的环境变量 java.system.class.loader 在此方法第一次执行之前被设置的话,这个属性的值代表的 ClassLoader 将会被作为指定的系统类加载器,这个被指定的系统类加载器要求提供一个公有的接收一个 ClassLoader 参数的构造方法,这个 CLassLoader 会被作为此类的父类加载器。这个类的加载器实例会被默认的系统类加载器创建,返回的系统类加载器就是系统类加载。
2、加载器的实现细节
public static ClassLoader getSystemClassLoader() {
// 尝试初始化系统类加载器,我们的重点在这个地方
initSystemClassLoader();
// 省略代码 ....
return scl;
}
继续深入 initSystemClassLoader() 方法,这是一个静态的线程安全的方法,其方法定义为
private static synchronized void initSystemClassLoader() {
// ...
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
// scl: System ClassLoader 的简称
scl = l.getClassLoader();}
可以观察到 Launcher 的创建通过 sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
创建,这里面我们就来看看 getLauncher() 的内部是如何实现,下面的代码示例是 Launcher 的静态获取方法,以及其构造方法。在其构造方法中创建了拓展类加载器以及系统类加载器。
public class Launcher {private static Launcher launcher = new Launcher();
private ClassLoader loader;
public static Launcher getLauncher() {return launcher;}
public Launcher() {// 创建拓展类加载器通过 getExtClassLoader() 方法
Launcher.ExtClassLoader var1;
try {var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {throw new InternalError("Could not create extension class loader", var10);
}
// 创建应用类加载器通过 getAppClassLoader(ClassLoader var1)
// 这里的 var1 指的是拓展类加载,将会被作为系统类加载器的父类加载器
try {this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
scl = AccessController.doPrivileged(new SystemClassLoaderAction(scl));
}
// 以下代码忽略
}
可以看到 Launcher 这个类的构造方法中初始化了拓展类加载器和应用类加载器,在创建应用类加载器的时候,将拓展类加载器作为参数传到应用类加载器的方法中, 即 Launcher.AppClassLoader.getAppClassLoader(var1);
下面我们来分别看看这两个加载器的实现过程。
在 Thread.currentThread().setContextClassLoader(this.loader);
这行代码中也印证了注释中阐述的 at which point it creates the system class loader and sets it as the context class loader of the invoking Thread.
2.1、拓展类加载器的创建
Launcher 方法中通过 Launcher.ExtClassLoader.getExtClassLoader();
放创建拓展类加载器的实例,其核心代码如下:
// 这里使用单例设计模式的 DCL 的方法,保证单例
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {if (instance == null) {
Class var0 = Launcher.ExtClassLoader.class;
synchronized(Launcher.ExtClassLoader.class) {if (instance == null) {instance = createExtClassLoader();
}
}
}
return instance;
}
// 创建 ExtClassloader
private static Launcher.ExtClassLoader createExtClassLoader() throws IOException {
try {return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {public Launcher.ExtClassLoader run() throws IOException {
// 获取需要加载的类的文件目录信息
File[] var1 = Launcher.ExtClassLoader.getExtDirs();
int var2 = var1.length;
// 分别注册拓展类需要加载的目录
for(int var3 = 0; var3 < var2; ++var3) {MetaIndex.registerDirectory(var1[var3]);
}
// 返回拓展类加载器
return new Launcher.ExtClassLoader(var1);
}
});
} catch (PrivilegedActionException var1) {throw (IOException)var1.getException();}
}
// 获取拓展类加载器的需要加载的目录
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;
}
通过上面的核心代码的注释可以明确的看到 拓展类加载器读取了 java.ext.dirs
的环境变量的值,所以我们这里可以通过在 java 的启动参数添加环境变量就可以修改环境变量加载的目录,比如 java -Djava.ext.dirs=/tmp/classes
加载并注册之后通过 new 的方式返回了拓展类加载器
2.2、系统类加载器的创建
系统类加载器和拓展类加载器加载方式类似,有点明显的区别是,创建系统类加载器的时候,是将拓展类加载器作为参数传入,Launcher.AppClassLoader.getAppClassLoader(extClassload);
而在构造应用类加载器 的时候,将拓展类加载作为构造参数传入我们这里可以大胆的猜测,这里试讲拓展类加载器作为应用类加载的而设置,系统类加载器的构造方法如下:
AppClassLoader(URL[] var1, ClassLoader var2) {super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}
// 其 super() 方法定义为
public URLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {super(parent);
// ........
}
创建系统类加载器和创建拓展类加载的过程类似,这里读者可以自行的浏览源码
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);
}
});
}
2.3、SystemClassLoaderAction
在拓展类加载器和系统类加载器加载完成,并且将会系统类加载器设置当前线程的上下文类加载器之后,源码中还执行了如下代码
if (l != null) {
Throwable oops = null;
scl = l.getClassLoader();
try {
scl = AccessController.doPrivileged(new SystemClassLoaderAction(scl));
} catch (PrivilegedActionException pae) {oops = pae.getCause();
if (oops instanceof InvocationTargetException) {oops = oops.getCause();
}
}
核心是:scl = AccessController.doPrivileged(new SystemClassLoaderAction(scl));
SystemClassLoaderAction 并不是一个类加载器,记得在开头的注释中,我们说 如果系统的环境变量 java.system.class.loader 在此方法第一次执行之前被设置的话,这个属性的值代表的 ClassLoader 将会被作为指定的系统类加载器,这个被指定的系统类加载器要求提供一个公有的接收一个 ClassLoader 参数的构造方法,这个 CLassLoader 会被作为此类的父类加载器。 这个 SystemClassLoaderAction
主要就是判断是否配置了 java.system.class.loader 属性,如创建了通过类的反射创建此加载器的实例,将上面创建的系统类加载器作为其父类加载器构建,并返回作为新的类加载器.
明白这个之后,下面的 SystemClassLoaderAction 代码块就非常简单了
class SystemClassLoaderAction implements PrivilegedExceptionAction<ClassLoader> {
private ClassLoader parent;
SystemClassLoaderAction(ClassLoader parent) {this.parent = parent;}
public ClassLoader run() throws Exception {String cls = System.getProperty("java.system.class.loader");
if (cls == null) {return parent;}
// 通过反射获取存在 CLassLoader 作为唯一参数的构造器,这也是注释中为什么要求 说
// java.system.class.loader 指向的类加载器需要有一个公有的且只有一个 CLassLoader 作为形参的构造器的原因
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;
}
3、汇总
- 通过 Launcher 的方法,我们熟悉了拓展类加载器以及系统类加载器的实现细节,以及拓展类加载器作为系统类加载器父类的实现方法
- 在两个类加载的实现内部,也可以明确的看到
java.ext.dirs
以及java.class.path
的作用,在必要的时候,可以通过设置参数来达到自己加载其他地方 Class 文件的目的 - Reflection.getCallerClass(); 可以获取到调用当前方法的 Class 信息