深入理解Java虚拟机Launcher-源码分析

4次阅读

共计 7091 个字符,预计需要花费 18 分钟才能阅读完成。

更多 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 信息
正文完
 0