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

类加载器源码分析

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

类加载体系

上图呈现是源码级别的类加载体系,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();
    }
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java一日一条

Java代码编译和执行的整个过程

JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:

611
来自专栏我是攻城师

Apache Pig学习笔记之内置函数(三)

3584
来自专栏你不就像风一样

Jsoup+FastJson制作新闻数据接口-Demo

经常用到 编写出来直接拿来用 这个适合在服务端结合servlet来做接口:需要下载jsoup+fastjson两个包 Jsoup使用手册:http:/...

602
来自专栏微信公众号:Java团长

Java类加载器详解(上)

我们知道,新建一个Java对象的时候,JVM要将这个对象对应的字节码加载到内存中,这个字节码的原始信息存放在classpath(就是我们新建Java工程的bin...

1292
来自专栏owent

理解Protobuf的数据编码规则

之前用Google的Protobuf感觉真是个很好用的东西,于是抽时间研究了下他的数据的存储方式,以后可以扩展其他语言的解析器。其实与其说是研究,不如说是翻译。...

681
来自专栏我是攻城师

理解ClassNotFoundException与NoClassDefFoundError的区别

但是你知道他们的区别吗?以及什么情况下发生上面的异常? 如果你还不清楚,那么不着急,我们来仔细分析一下:

732
来自专栏spring源码深度学习

java基础io流——配角也风流(不求甚解)

PrintStream是OutputStream的子类,PrintWriter是Writer的子类,两者处于对等的位置上,所以它们的API是非常相似的。

702
来自专栏逸鹏说道

Python3 与 C# 基础语法对比(List、Tuple、Dict、Set专栏-新排版)

在线预览:http://github.lesschina.com/python/base/pop/3.listtupledict_set.html

1595
来自专栏Java Edge

类加载器与双亲委派模型1 类加载器 2 双亲委派模型

类加载器(ClassLoader)是Java语言的一项创新,也是Java流行的一个重要原因。 在类加载的第一阶段“加载”过程中,需要通过一个类的全限定名来获取...

762
来自专栏java一日一条

Java代码编译和执行的整个过程

JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:

622

扫码关注云+社区