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