美文网首页java jvm
Java虚拟机--类加载器源码

Java虚拟机--类加载器源码

作者: 贾博岩 | 来源:发表于2018-05-13 09:17 被阅读339次

    类加载器源码分析

    下面,我们就来深入的学习下类加载器的源码,看看到底做了哪些事情?

    类加载体系

    上图呈现是源码级别的类加载体系,ClassLoader是基类,所有的类加载器都需要继承它(启动类加载器除外)。

    首先,我们通过上文中的测试类来举例,一点点剖析类加载的流程。

    创建一个包下普通的类:com.jiaboyan.test.ObjectTest

    public class ObjectTest {
    }
    

    将此类编译,并打包处理:

    jar cvf ObjectTest.jar com\jiaboyan\test\ObjectTest.class
    

    将此jar包放入<Java_Runtime_Home>/lib/ext目录下;

    编写测试用例:

    public class JVMTest5 {
       public static void main(String[] agrs) throws ClassNotFoundException {
           Class clazz = Class.forName("com.jiaboyan.test.ObjectTest");
           System.out.println(clazz.getClassLoader());
       }
    }
    

    使用Class.forName来加载com.jiaboyan.test.ObjectTest类,看内部具体流程。

    类加载流程

    (1)进入到Class内部,可以看到实际调用了native修饰的forName0()方法。

    public static Class<?> forName(String className) throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }
    
    private static native Class<?> forName0(String name, boolean initialize,
                                            ClassLoader loader,
                                            Class<?> caller) throws ClassNotFoundException;
    

    (2)通过debug来看,在forName0()后又调用了AppClassLoader类的loadClass(String var1, boolean var2)方法。

    public Class loadClass(String var1, boolean var2) throws ClassNotFoundException {
        int var3 = var1.lastIndexOf(46);
        if (var3 != -1) {
            SecurityManager var4 = System.getSecurityManager();
            if (var4 != null) {
                var4.checkPackageAccess(var1.substring(0, var3));
            }
        }
        return super.loadClass(var1, var2);
    }
    

    在方法的末尾,调用父类中的loadClass(String name, boolean resolve)方法,也就是ClassLoader类;

    (3)通过debug来看,在forName0()后又调用了ClassLoader类的loadClass(String name, boolean resolve)方法。

    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);
    }
    } catch (ClassNotFoundException e) {
    //抛出异常无需理会
    }

                if (c == null) {
                    long t1 = System.nanoTime();
                    //如果都没有找到,则通过findClass去查找并加载
                    c = findClass(name);
                    .....
                }
            }
            //是否需要在加载时进行解析
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
    

    loadClass源码所示:当类加载请求到来时,先从缓存中查找该类对象,如果存在则直接返回,如果不存在则交给该类加载器的父类加载器去加载,倘若没有父类加载器则交给顶级启动类加载器去加载,最后仍没有找到,则使用findClass()方法去加载。

    实际流程是,类加载请求进来时,this为AppClassLoader对象,判断AppClassLoader对象的parent父类加载器是否为空。根据双亲委派模型得知,此时的parent一定为ExtClassLoader对象。调用ExtClassLoader的loadClass(String name, boolean resolve)方法。

    通过源码得知,ExtClassLoader继承ClassLoader抽象类,并且没有重写loadClass(String name, boolean resolve)方法。那么,此时又进入到了上面的loadClass(String name, boolean resolve)中。不过,此时的this是ExtClassLoader对象。

    ExtClassLoader对象的parent父类加载器为null,调用findBootstrapClassOrNull(String name)方法,使用顶层启动类加载器去加载com.jiaboyan.test.ObjectTest类。

    由于顶层启动类加载器是C++实现,我们无法直接获取到其引用,最终调用到了native修饰的findBootstrapClass(String name)方法。

    由于,我们将ObjectTest.jar放在了<Java_Runtime_Home>/lib/ext目录下,所以顶层启动类加载器加载不到com.jiaboyan.test.ObjectTest类,继而抛出异常,返回到ExtClassLoader对象的loadClass(String name, boolean resolve)方法中来。

    (4)接下来,继续调用findClass(name)方法。

    protected Class<?> findClass(final String name) throws ClassNotFoundException{
        try {
            return AccessController.doPrivileged(
                new PrivilegedExceptionAction<Class>() {
                    public Class run() throws ClassNotFoundException {
                        String path = name.replace('.', '/').concat(".class");
                        Resource res = ucp.getResource(path, false);
                        if (res != null) {
                            try {
                                //生成class对象
                                return defineClass(name, res);
                            } catch (IOException e) {
                                throw new ClassNotFoundException(name, e);
                            }
                        } else {
                            throw new ClassNotFoundException(name);
                        }
                    }
                }, acc);
        } catch (java.security.PrivilegedActionException pae) {
            throw (ClassNotFoundException) pae.getException();
        }
    }
    

    由于现在的this为ExtClassLoader对象,所以我们调用ExtClassLoader对象的findClass(final String name)方法。通过查看源码发现,ExtClassLoader并没有findClass方法,不过再其父类URLClassLoader中有实现(代码如上)。

    (5)调用defineClass(String name, Resource res)方法。

    private Class defineClass(String name, Resource res) throws IOException {
        long t0 = System.nanoTime();
        int i = name.lastIndexOf('.');
        URL url = res.getCodeSourceURL();
        if (i != -1) {
            String pkgname = name.substring(0, i);
            Manifest man = res.getManifest();
            if (getAndVerifyPackage(pkgname, man, url) == null) {
                try {
                    if (man != null) {
                        definePackage(pkgname, man, url);
                    } else {
                        definePackage(pkgname, null, null, null, null, null, null, null);
                    }
                } catch (IllegalArgumentException iae) {
                    if (getAndVerifyPackage(pkgname, man, url) == null) {
                        throw new AssertionError("Cannot find package " + pkgname);
                    }
                }
            }
        }
        java.nio.ByteBuffer bb = res.getByteBuffer();
        if (bb != null) {
            //获取资源内的字节流数组:
            CodeSigner[] signers = res.getCodeSigners();
            CodeSource cs = new CodeSource(url, signers);
            sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
            return defineClass(name, bb, cs);
        } else {
            //获取资源内的字节流数组:
            byte[] b = res.getBytes();
            CodeSigner[] signers = res.getCodeSigners();
            CodeSource cs = new CodeSource(url, signers);
            sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
            return defineClass(name, b, 0, b.length, cs);
        }
    }
    

    defineClass(String name, Resource res)方法是用来将byte字节流解析成JVM能够识别的Class对象,通过这个方法不仅能够通过class文件实例化class对象,也可以通过其他方式实例化class对象,如通过网络接收一个类的字节码,然后转换为byte字节流创建对应的Class对象。

    最后调用了defineClass(String name, java.nio.ByteBuffer b,CodeSource cs)方法,具体内部细节笔者不在详细陈序,需要说明的是:Class对象依旧使用了native修饰的方法来完成,内部细节无从得知。

    private native Class defineClass2(String name, java.nio.ByteBuffer b,
                                      int off, int len, ProtectionDomain pd,
                                      String source);
    

    (6)至此ExtClassLoader对象的加载阶段就此结束,Class对象返回。当返回到AppClassLoader对象中时,c并不为null,所以无需再次调用c = findClass(name)方法,整个类加载完成。

    通过上述源码可知,当我们自己定义一个类加载器时候,无需重写loadClass()方法,直接重写自定义的findClass(String name)即可。父类加载器加载失败时候,最终都会走到findClass(String name)中来。

    说完了类加载主体流程,接下来我们来研究一点细节的东西!!!!

    此时,将文章拉回上面源码体系截图中,我们来看看SecureClassLoader、URLClassLoader类起到了哪些作用。

    SercureClassLoader

    SercureClassLoader继承ClassLoader,扩展了ClassLoader类的功能,增加对代码源和权限定义类的验证。

    URLClassLoader

    URLClassLoader继承SercureClassLoader,实现了ClassLoader中定义的方法,例如:findClass()、findResource()等。

    在URLClassLoader中有一个成员变量ucp--URLClassPath对象,URLClassPath的功能是通过传入的路径信息获取要加载的字节码,字节码可以是在.class文件中、可以是在.jar包中,也可以是在网络中。

    public URLClassLoader(URL[] urls) {
        super();
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkCreateClassLoader();
        }
        ucp = new URLClassPath(urls);
        this.acc = AccessController.getContext();
    }
    

    在URLClassPath构造中,需要传入URL[]数组,通过这个URL[]数组中所指定的位置信息,去加载对应的文件。在URLClassPath内部会根据传递的路径是文件地址、jar包地址还是网络地址来进行判断,来生成对应Loader。

    public URLClassPath(URL[] var1, URLStreamHandlerFactory var2) {
        this.path = new ArrayList();
        this.urls = new Stack();
        this.loaders = new ArrayList();
        this.lmap = new HashMap();
        this.closed = false;
    
        for(int var3 = 0; var3 < var1.length; ++var3) {
            this.path.add(var1[var3]);
        }
    
        this.push(var1);
        if (var2 != null) {
            this.jarHandler = var2.createURLStreamHandler("jar");
        }
    

    }

    根据路径的不同,来生成不同的Loader:

    private URLClassPath.Loader getLoader(final URL var1) throws IOException {
        try {
            return (URLClassPath.Loader)AccessController.doPrivileged(new PrivilegedExceptionAction<URLClassPath.Loader>() {
                public URLClassPath.Loader run() throws IOException {
                    String var1x = var1.getFile();
                    if (var1x != null && var1x.endsWith("/")) {
                        return (URLClassPath.Loader)("file".equals(var1.getProtocol()) ? new URLClassPath.FileLoader(var1) : new URLClassPath.Loader(var1));
                    } else {
                        return new URLClassPath.JarLoader(var1, URLClassPath.this.jarHandler, URLClassPath.this.lmap);
                    }
                }
            });
        } catch (PrivilegedActionException var3) {
            throw (IOException)var3.getException();
        }
    }

    相关文章

      网友评论

        本文标题:Java虚拟机--类加载器源码

        本文链接:https://www.haomeiwen.com/subject/qvikdftx.html