初探JVM-ClassLoader源码(一)

2014-11-24 08:20:04 · 作者: · 浏览: 0

之前简单介绍了一下JVM中的ClassLoader (见《JVM类加载机制-ClassLoader》),现在我们再来看看ClassLoader的源码

ClassLoader

首先,我们看看ClassLoader的构造方法,如果不传入参的话,默认父加载为SystemClassLoader默认加载器。

protected ClassLoader() {
        this(checkCreateClassLoader(),getSystemClassLoader());
}

我们再细看一层,发现SystemClassLoader原来要到sun.misc.Launcher里面去。其实JVM用到的3个类加载器,有两个是sun.misc.Launcher里面的AppClassLoader和ExtClassLoader,还有一个BootstrapLoader是C++写的,这里我们就不细究了。

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();
                }
            }
            sclSet = true;
        }
}

我们再来看看loadClass(),这个无疑是整个ClassLoader的重点,其处理逻辑为:

1. 调用 findLoadedClass()检查是否已经加载类。

2. 在父类加载器上调用loadClass(),让父类先尝试加载(委派模型)。如果父类加载器为null,就是说到达顶层加载器了,则用内置类加载方法findBootstrapClass0()来加载。

3. 顶层加载器的findBootstrapClass0()也找不到class的话,就会抛出异常,此时再调用当前加载器的findClass()查找。

通过这个流程,能保证每个class只load一次,避免发生类冲突。而且按照委派模型,每次都从顶层ClassLoader开始加载,确保类加载的安全。

protected synchronized Class
  loadClass(String name, boolean resolve)throws ClassNotFoundException {     
        Class c = findLoadedClass(name);  // Check if it has already beenloaded
        if (c ==null) {
            try {
                if (parent !=null)
                    c = parent.loadClass(name,false);
                else
                    c = findBootstrapClass0(name);
            }
            catch (ClassNotFoundException e) {             
                c = findClass(name);  // If still not found, then invokefindClass
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
}
private native final Class findLoadedClass0(String name);
private native Class findBootstrapClass(String name);
private native void resolveClass0(Class c);
protected Class
  findClass(String name)throws ClassNotFoundException
        throw new ClassNotFoundException(name);
}

以上代码能看出,底层load和find的实现都不是Java写的,交给本地方法实现,由JVM管理。

另外你也许会疑惑,ClassLoader.findClass()里面竟然直接抛出异常。这是因为JVM一般的类都是由findBootstrapClass0()来查找的,如果找不到,就说明classpath中没有此class了,所以这里只能继续抛出ClassNotFoundException。

(所以我们看到ClassNotFoundException出现的时候,第一反应应该检查一下classpath的设置,检查该类的.class是否存在,检查jar包有没有部署好等等。)

其实findClass()是留给用户自定义的类加载器实现的。因为findBootstrapClass0()只会在classPath中查找.class文件,如果你通过别的方式(如网络),或者在别的路径(classpath以外的路径)加载,就要自定义ClassLoader,并通过重写findClass()来重新指向你的类。 如URLClassLoader

这里最好不要重写loadClass(),因为会损坏ClassLoader的委派模型,造成安全隐患。

另外,我们可以用代码来查看JVM加载器的层次结构。

JVM的ClassLoader是三层结构,分别是:BootstrapLoader加载系统类,ExtClassLoader加载扩展类,AppClassLoader加载应用类。

我们建一个测试类Inner4Test,然后循环打印出它的类加载器。

        Class
   c = Class.forName("com.jscai.classloader.Inner4Test");
        ClassLoader loader = c.getClassLoader();
        while (loader !=null) {
            System.out.println(loader.toString());
            loader = loader.getParent();
       }

console:

sun.misc.Launcher$AppClassLoader@79b0edb2

sun.misc.Launcher$ExtClassLoader@4ec57f88

这里清晰地打印出JVM类加载的层次,但只有AppClassLoader和ExtClassLoader,其实BootstrapLoader的缺席,是因为BootstrapLoader是C++写的,Java这边没法显示,所以认为null。

接着,我们看看UrlClassLoader是怎么实现自定义加载的。

URLClassLoader

URLClassLoader加载器用于从 指向JAR文件和目录的URL的搜索路径 来加载类和资源。相对于JVM的加载器只能加载