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

来源:互联网 发布:windows 10输入法切换 编辑:程序博客网 时间:2024/05/17 23:29

一、三种基本的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.Usercom.own.test.ClassLoaderTest$1@7f31245asun.misc.Launcher$AppClassLoader@18b4aac2sun.misc.Launcher$ExtClassLoader@135fbaa4null

上面的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设计原理与实现》--封亚飞















原创粉丝点击