更多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 信息