结合源码分析Java中的三种ClassLoader的关系

一、三种基本的ClassLoader

Java体系中定义了三种类加载器:BootstrapClassLoader、ExtClassLoader、AppClassLoader。需要注意的是,ExtClassLoader、AppClassLoader是位于sun.misc.Launcher中定义的内部类,是在Launcher的构造函数中创建的,下文会详细说明。

1、BootstrapClassLoader

引导类加载器,加载指定的JDK核心类库,在Java程序中无法直接引用,因为该加载器是在JVM中用c++编写的。负责加载已下核心类库:

%JAVA_HOME%/jre/lib目录

-Xbootclasspath参数指定的目录

系统属性sun.boot.class.path指定的目录总特定名称的jar包

2、ExtClassLoader

拓展加载器,加载拓展类。加载已下两种类库:

%JAVA_HOME%/jre/lib/ext目录

系统属性java.ext.dirs所指定的目录中的所有类库

3、AppClassLoader

系统类加载器。加载Java应用程序类库,加载类库的路径由系统环境变量ClassPath,-cp或系统属性java.class.path指定

二、关于自定义ClassLoader

public class ClassLoaderTest {

    private static final String LOAD_PATH = "D:\\ideaWork\\blog\\out\\production\\classes\\com\\blog\\po\\";//自定义加载路径

    public static void main(String[] args) throws Exception {
        ClassLoader classLoader=new ClassLoader() {

            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf(".") + 1)+".class";
//                    String fileName = name + ".class";
                    InputStream inputStream = null;
                    try {
                        inputStream = new FileInputStream(new File(LOAD_PATH + fileName));
                    } catch (Exception e) {
                        inputStream = null;
                    }
                    //当加载Class,JVM都会先去加载它的父类。所以,Object等父类应该由父类加载器来加载
                    if (inputStream == null) {
                        //如果不存在该文件,使用父类加载器进行加载
                        return super.loadClass(name);
                    }
                    byte[] bytes = new byte[inputStream.available()];
                    inputStream.read(bytes);
                    return defineClass(name, bytes, 0, bytes.length);//加载类
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return super.loadClass(name);
            }
        };

        Object object = classLoader.loadClass("com.blog.po.User").newInstance();
        System.out.println(object.getClass());
        System.out.println(object.getClass().getClassLoader());
        System.out.println(object.getClass().getClassLoader().getParent());
        System.out.println(object.getClass().getClassLoader().getParent().getParent());
        System.out.println(object.getClass().getClassLoader().getParent().getParent().getParent());
    }
}

com.blog.po.User是我放在自定义路径下的另一个class文件


运行结果:

class com.blog.po.User
com.own.test.ClassLoaderTest$1@7f31245a
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@135fbaa4
null

上面的Demo演示了如何用自定义的ClassLoader加载自定义的类。当加载Class,JVM都会先去加载它的父类。所以,Object等父类应该由父加载器来加载。事实上,如果不用父加载器加载的话,会出现类似与java.io.FileNotFoundException: \**\Object.class (系统找不到指定的文件。)这样的错误。

从上面的运行结果可以看到,com.blog.po.User这个类是用我们自定义的类加载器com.own.test.ClassLoaderTest$1@7f31245a加载的。而他的父加载链路为sun.misc.Launcher$AppClassLoader@18b4aac2、sun.misc.Launcher$ExtClassLoader@135fbaa4,而ExtClassLoader的父类加载器为BootstrapClassLoader上面的运行结果之所以为null,就是因为这个引导类加载器并不是由Java写的,在Java程序中是引用不到的。

三、双亲委派机制的理解

1、并不是属于继承关系

     上面提到过父加载器,并不是意味着这三种类加载器是属于继承关系。并且,由于BootstrapClassLoader是C++编写的,在Java程序中是无法继承的。而双亲委派机制更重要的是一种委派机制:当加载一个类是,先用类中定义的一个名为parent的ClassLoader(也就是当前加载器的父加载器)去加载,如果父加载器加载不到,再由自己加载。

 ClassLoader中的源码为:


protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先加载器会先检查这个类是否已经被加载过,如果是,从缓存的结果中直接返回该Class
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);//如果父加载器不为空,就用父加载器去加载
                    } else {
                        c = findBootstrapClassOrNull(name);//如果父加载器为空,那么就用BootstrapClassLoader去加载
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {//从这里可以看到,只有当父加载器加载不到时,才会自己去加载
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

而源码中的parent这个类内部变量为:

  // The parent class loader for delegation
    // Note: VM hardcoded the offset of this field, thus all new fields
    // must be added *after* it.
    private final ClassLoader parent;

它是在ClassLoader初始化时通过构造函数传入并赋值的,具体源码如下:

private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();
            package2certs = new ConcurrentHashMap<>();
            domains =
                Collections.synchronizedSet(new HashSet<ProtectionDomain>());
            assertionLock = new Object();
        } else {
            // no finer-grained lock; lock on the classloader instance
            parallelLockMap = null;
            package2certs = new Hashtable<>();
            domains = new HashSet<>();
            assertionLock = this;
        }
    }

2、三个基本加载器之间的关系


由于ExtClassLoader、AppClassLoader是位于sun.misc.Launcher中定义的内部类,在调用Launcher的默认构造函数中创建的,而Launcher是伴随虚拟机初始化时加载Java主类时调用位于/src/share/vm/classfile/systemDictionary.cpp的SystemDictionary::compute_java_system_loader()函数创建的,其中调用了ClassLoader的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;
        }
    }

可以看到,通过sun.misc.Launcher l = sun.misc.Launcher. getLauncher();完成创建,然后转入这个函数:

public static Launcher getLauncher() {
        return launcher;
    }

而Launcher在初始化时会持有一个自身的引用:

private static Launcher launcher = new 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);
        
        //其他代码。。。。

    }

这里我们先分析ExtClassLoader的创建:


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

其中的调用  new Launcher.ExtClassLoader(var0);完成创建,var0是由getExtDirs()返回的文件数组,也就是上文提到的加载路径,其具体实现如下:

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

然后调用了ExtClassLoader的构造函数:

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

这里调用父类的构造函数完成初始化,传入的参数中, 我们需要关注的是第二个参数传入的是null,而父类URLClassLoader的构造函数实现为:

public URLClassLoader(URL[] urls, ClassLoader parent,
                          URLStreamHandlerFactory factory) {
        super(parent);
       //其他代码
    }

protected SecureClassLoader(ClassLoader parent) {
        super(parent);
        //...
    }

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


最终进入到ClassLoader的构造函数中:

private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();
            package2certs = new ConcurrentHashMap<>();
            domains =
                Collections.synchronizedSet(new HashSet<ProtectionDomain>());
            assertionLock = new Object();
        } else {
            // no finer-grained lock; lock on the classloader instance
            parallelLockMap = null;
            package2certs = new Hashtable<>();
            domains = new HashSet<>();
            assertionLock = this;
        }
    }

其实我们关注的只有一行代码:this.parent = parent;

而ExtClassLoader传入的是null,也就是说,ExtClassLoader的父加载器为null。从上文中引用的ClassLoader的loadClass函数中有一段代码:

if (parent != null) {
                        c = parent.loadClass(name, false);//如果父加载器不为空,就用父加载器去加载
                    } else {
                        c = findBootstrapClassOrNull(name);//如果父加载器为空,那么就用BootstrapClassLoader去加载
                    }
ExtClassLoader的parent即父加载器为null,所以当调用loadClass时,会走上面的if语句中的下面那条路,也就是通过BootstrapClassLoader引导类加载器加载,这也是为什么说BootstrapClassLoader是ExtClassLoader父加载器的原因。


然后是AppClassLoader的创建,再次引用Launcher的构造函数的部分代码:

var1 = Launcher.ExtClassLoader.getExtClassLoader();
  //省略部分代码     
 this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);//传入的就是上面的ExtClassLOader

后续调用链路为:

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

getAppClassLoader()是通过AppClassLoader构造函数初始化的,我们需要关注的是其构造函数的第二个入参,也就是会被复制给parent变量的那个参数,从Launcher构造函数中可以看到, 赋值给AppClassLoader的parent的是ExtClassLoader的实例,说明AppClassLoader的父加载器就是ExtClassLoader!

至此完成AppClassLoader、ExtClassLoader的创建。


3、findClass和loadClass的区别

//todo



//如有错误,欢迎指正


参考:

《揭秘Java虚拟机-JVM设计原理与实现》--封亚飞















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值