JVM类加载器

Posted beiluowuzheng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM类加载器相关的知识,希望对你有一定的参考价值。

现在,我们来思考一个问题,类加载器负责加载Java核心库的类,加载我们编写的应用类,但类加载器本身也是一个类,那么又是谁来加载类加载器呢?

内建于JVM中的根类加载器会加载java.lang.ClassLoader以及其他的Java平台类。 当JVM启动时,一块特殊的机器码会运行,它会加载扩展类加载器与应用类加载器,这块特殊的机器码叫做根类加载器(Bootstrap)。

根类加载器并不是Java类,而其他的加载器则都是Java类。

根类加载器是特定于平台的机器指令,它负责开启整个加载过程。

所有类加载器(除了根类加载器)都被实现为Java类,不过,总归要有一个组件来加载第一个Java类加载器。从而让整个加载过程能够顺利进行下去,加载第一个纯Java类加载器就是根类加载器的职责。

根类加载器还会负责加载供JRE正常运行所需要的基本组件,这包括java.util与java.lang包中的类等等。

对于JVM类加载器来说有一个特点,如果Launcher是有A加载器所加载的,A加载器会尝试加载这个类所依赖的所有组件,也包括静态类。JDK提供了java.system.class.loader,可以通过这个属性显式的去修改我们的应用类加载器,即默认的应用类加载器不再是AppClassLoader,而是我们自己定义的ClassLoader。

我们修改MyTest23如下:

package com.leolin.jvm;

import sun.misc.Launcher;

public class MyTest23 {
    public static void main(String[] args) {
        System.out.println(System.getProperty("sun.boot.class.path"));
        System.out.println(System.getProperty("java.ext.dirs"));
        System.out.println(System.getProperty("java.class.path"));
        System.out.println(ClassLoader.class.getClassLoader());
        System.out.println(Launcher.class.getClassLoader());
        System.out.println(System.getProperty("java.system.class.loader"));
        System.out.println(MyTest23.class.getClassLoader());
        System.out.println(MyTest16.class.getClassLoader());
        System.out.println(ClassLoader.getSystemClassLoader());
    }
}

    

编译上面的程序后,我们到工程的classpath下执行如下代码:

D:Fworkjava_spacejvm-lecture	argetclasses>java -Djava.system.class.loader=com.leolin.jvm.MyTest16 com.le
olin.jvm.MyTest23
Error occurred during initialization of VM
java.lang.Error: java.lang.NoSuchMethodException: com.leolin.jvm.MyTest16.<init>(java.lang.ClassLoader)
        at java.lang.ClassLoader.initSystemClassLoader(Unknown Source)
        at java.lang.ClassLoader.getSystemClassLoader(Unknown Source)
Caused by: java.lang.NoSuchMethodException: com.leolin.jvm.MyTest16.<init>(java.lang.ClassLoader)
        at java.lang.Class.getConstructor0(Unknown Source)
        at java.lang.Class.getDeclaredConstructor(Unknown Source)
        at java.lang.SystemClassLoaderAction.run(Unknown Source)
        at java.lang.SystemClassLoaderAction.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.lang.ClassLoader.initSystemClassLoader(Unknown Source)
        at java.lang.ClassLoader.getSystemClassLoader(Unknown Source)

    

程序要求我们给MyTest16添加一个构造方法,这个构造方法要求传入一个ClassLoader类,作为MyTest16的委托父加载器。于是,我们给MyTest16添加如下的代码:

public MyTest16(ClassLoader parent) {
	super(parent);
}

  

重新编译我们的项目生成新的MyTest16的class文件,然后我们到工程的类路径下执行原先的代码:

D:Fworkjava_spacejvm-lecture	argetclasses>java -Djava.system.class.loader=com.leolin.jvm.MyTest16 com.le
olin.jvm.MyTest23
D:FworkJDKJRE1.8lib
esources.jar;D:FworkJDKJRE1.8lib
t.jar;D:FworkJDKJRE1.8libsunrsasign.jar
;D:FworkJDKJRE1.8libjsse.jar;D:FworkJDKJRE1.8libjce.jar;D:FworkJDKJRE1.8libcharsets.jar;D:F
workJDKJRE1.8libjfr.jar;D:FworkJDKJRE1.8classes
D:FworkJDKJRE1.8libext;C:WindowsSunJavalibext
.;D:FworkJDKJDK1.7lib;D:FworkJDKJDK1.7lib	ools.jar
null
null
com.leolin.jvm.MyTest16
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$AppClassLoader@18b4aac2
com.leolin.jvm.MyTest16@6d06d69c

  

当我们调用ClassLoader.getSystemClassLoader()时,返回我们自定义的类加载器MyTest16,而我们的MyTest16是由应用类加载器所加载的,而MyTest23是MyTest16委托父加载器去加载的。

下面,我们来看一下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.
     *
     * <p> If a security manager is present, and the invoker‘s class loader is
     * not <tt>null</tt> and the invoker‘s class loader is not the same as or
     * an ancestor of the system class loader, then this method invokes the
     * security manager‘s {@link
     * SecurityManager#checkPermission(java.security.Permission)
     * <tt>checkPermission</tt>} method with a {@link
     * RuntimePermission#RuntimePermission(String)
     * <tt>RuntimePermission("getClassLoader")</tt>} permission to verify
     * access to the system class loader.  If not, a
     * <tt>SecurityException</tt> will be thrown.  </p>
     *
     * @return  The system <tt>ClassLoader</tt> for delegation, or
     *          <tt>null</tt> if none
     *
     * @throws  SecurityException
     *          If a security manager exists and its <tt>checkPermission</tt>
     *          method doesn‘t allow access to the system class loader.
     *
     * @throws  IllegalStateException
     *          If invoked recursively during the construction of the class
     *          loader specified by the "<tt>java.system.class.loader</tt>"
     *          property.
     *
     * @throws  Error
     *          If the system property "<tt>java.system.class.loader</tt>"
     *          is defined but the named class could not be loaded, the
     *          provider class does not define the required constructor, or an
     *          exception is thrown by that constructor when it is invoked. The
     *          underlying cause of the error can be retrieved via the
     *          {@link Throwable#getCause()} method.
     *
     * @revised  1.4
     */
    @CallerSensitive
    public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        }
        return scl;
    }

  

这里简单的翻译下getSystemClassLoader方法的注释:

返回一个基于委托模式的系统类加载器。它是新的类加载器实例的默认委托父加载器,这个类加载器通常用于启动应用。

在较早的运行期时这个方法会被首次调用,此时它会创建系统类加载器并将其设置为调用线程的上下文类加载器。

默认的系统类加载器是与ClassLoader类实现相关的一个实例。

当这个方法第一次被调用的时候,如果”java.system.class.loader”这个系统属性是被定义的,那么这个属性的值便会被作为被返回的系统类加载器的名字。这个类使用默认的系统类加载器进行加载,并且必须定义一个public的接收单个类型为ClassLoader参数的构造方法,这个ClassLoader参数会被委托父加载器。一个实例接下来会通过这个构造方法被创建,而默认的系统类加载器作为参数传入。而所生成的类加载器会被定义成系统类加载器。

忽略上面的安全管理器SecurityManager检查,我们看下这个方法里面的代码,主要是initSystemClassLoader()

private static synchronized void initSystemClassLoader() {
	if (!sclSet) {
		if (scl != null)
			throw new IllegalStateException("recursive invocation");
		sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
		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();
				}
			}
			if (oops != null) {
				if (oops instanceof Error) {
					throw (Error) oops;
				} else {
					// wrap the exception
					throw new Error(oops);
				}
			}
		}
		sclSet = true;
	}
}

  

sclSet和scl都是静态变量,sclSet是个boolean类型的标志位,scl是ClassLoader类型的,二者一般配合使用,如果sclSet为true,代表scl的值不为空,即默认的系统类加载器已被初始化。

在initSystemClassLoader中,会先调用Launcher类的一个静态方法getLauncher(),这个方法会返回一个已经创建好的Launcher实例,我们来看看Launcher的构造方法:  

public Launcher() {
	Launcher.ExtClassLoader var1;
	try {
		var1 = Launcher.ExtClassLoader.getExtClassLoader();
	} catch (IOException var10) {
		throw new InternalError("Could not create extension class loader", var10);
	}

	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);
}

 

在Launcher的构造方法中,会先调用ExtClassLoader的静态方法getExtClassLoader()获得一个扩展类加载器的实例,然后将其作为应用类加载器的父加载器,传给AppClassLoader的静态方法getAppClassLoader(),在获取到应用类加载器后,赋给Launcher的成员变量loader。最后又将线程上下文加载器的值设置为成员变量loader。线程上下文类加载器是一个很重要的概念,在后续的章节还会介绍。

我们节选了ExtClassLoader一部分代码,来看下ExtClassLoader是如何生成的:

static class ExtClassLoader extends URLClassLoader {
        public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
            final File[] var0 = getExtDirs();

            try {
                return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
                    public Launcher.ExtClassLoader run() throws IOException {
                        int var1 = var0.length;

                        for(int var2 = 0; var2 < var1; ++var2) {
                            MetaIndex.registerDirectory(var0[var2]);
                        }

                        return new Launcher.ExtClassLoader(var0);
                    }
                });
            } catch (PrivilegedActionException var2) {
                throw (IOException)var2.getException();
            }
        }
		……

        public ExtClassLoader(File[] var1) throws IOException {
            super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
            SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
        }

        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;
        }
		……
    }

  

在调用getExtClassLoader()的初期,会先调用getExtDirs()获取扩展类加载器所加载的路径,这里就会获取系统变量java.ext.dirs的值,getExtDirs()方法里会解析系统变量java.ext.dirs的值,然后返回一个File类型的数组。

接着在getExtClassLoader()方法中会调用AccessController.doPrivileged()这个本地方法,这个方法一般用于一些权限的检查,方法接收一个泛型的PrivilegedExceptionAction接口,PrivilegedExceptionAction要求实现一个run()方法,而在doPrivileged这个本地方法中,会去执行这个传入接口的run方法:

AccessController.java:

@CallerSensitive
public static native <T> T
	doPrivileged(PrivilegedExceptionAction<T> action)
	throws PrivilegedActionException;

  

PrivilegedExceptionAction.java:

public interface PrivilegedExceptionAction<T> {
    T run() throws Exception;
}

  

而在getExtClassLoader()方法中,PrivilegedExceptionAction接口的run方法会遍历之前获得的File类型的数组,最终调用ExtClassLoader的构造函数,传入File类型的数组,返回ExtClassLoader实例。

而我们的ExtClassLoader类继承于URLClassLoader,URLClassLoader类继承于SecureClassLoader,SecureClassLoader类继承于ClassLoader,所以ExtClassLoader和我们之前编写的类加载器一样,也是继承于ClassLoader。至此,我们分析完扩展类加载器实例的获取。

下面,我们再来看下应用类加载器的获取,其实这段代码和之前的扩展类加载器实例的获取类似,同样是获取java.class.path系统变量的值,然后将其转化为File数组。接着调用AccessController.doPrivileged进行安全检查,执行传入接口的run方法,在run方法中,我们将之前生成的扩展类加载器作为父加载器传入给AppClassLoader的构造方法:

static class AppClassLoader extends URLClassLoader {
	final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);

	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);
			}
		});
	}

	AppClassLoader(URL[] var1, ClassLoader var2) {
		super(var1, var2, Launcher.factory);
		this.ucp.initLookupCache(this);
	}
	……
}

  

通过上面扩展类加载器和应用类加载器代码的分析,证实了我们之前所说的,扩展类加载器是应用类加载器的父加载器。

在分析完Launcher实例的获取后,我们接着回到initSystemClassLoader()方法中:

private static synchronized void initSystemClassLoader() {
	if (!sclSet) {
		if (scl != null) {
			throw new IllegalStateException("recursive invocation");
		}

		Launcher var0 = Launcher.getLauncher();
		if (var0 != null) {
			……
			scl = var0.getClassLoader();

			try {
				scl = (ClassLoader)AccessController.doPrivileged(new SystemClassLoaderAction(scl));
			} catch (PrivilegedActionException var3) {
				……
			}
			……
		}

		sclSet = true;
	}

}

  

接着会调用Launcher实例的getClassLoader()方法,也就是我们之前在Launcher()构造方法中创建的应用类加载器实例,赋值给我们的scl,但这里还没有完,在赋值之后,还有个try/catch块,在块中又对scl进行了一次赋值,那么为什么要这么做呢?我们进到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;
        }

        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;
    }
}

  

可能有人看到run方法就恍然大悟了,这个方法中获取java.system.class.loader这个系统属性,我们可以通过这个系统属性定义默认的应用类加载器。而在run方法中,如果发现有定义默认的应用类加载器,则会调用应用类加载器的构造方法,这个构造方法要求传入一个ClassLoader的对象作为自定义默认的应用类加载器的父加载器。这也就是为什么我们第一次用MyTest16作为默认的应用类加载器时程序报错,要求我们定义一个传入ClassLoader的对象的MyTest16的构造方法。

而在这个方法的后面,依旧调用了Thread.currentThread().setContextClassLoader()这一方法设置线程的上下文加载器,在之前的Launcher()构造方法中,也有类似的行为,在获取到应用类加载器后,将其设置为线程的上下文类加载器。那么为什么要这么做呢?下一节将解释这一做法的原因。

以上是关于JVM类加载器的主要内容,如果未能解决你的问题,请参考以下文章

JVM虚拟机 类加载过程与类加载器

JVM虚拟机 类加载过程与类加载器

JVM类加载

JVM笔记二双亲委派机制

Jvm(57),类加载器----初次认识加载器

java jvm虚拟机类加载器