JVM

回顾《深入理解 Java 虚拟机》之类加载器

第四篇

Posted by ChenJY on February 8, 2019 | Viewed times

虚拟机团队把类加载阶段中的 “通过一个类的全限定名来获取描述此类的二进制字节流” 这个动作放到 Java 虚拟机外部去实现,以便让应用程序自己去决定如何获取所需要的类,实现这个动作的代码模块称为 “类加载器”

首先,先要知道什么是类加载器。简单说,类加载器就是根据指定全限定名称将 Class 文件加载到 JVM 内存,转为 Class 对象。

如果站在虚拟机的角度来看,只存在两种类加载器:

如果从 Java 程序员的角度看,还可以进一步细分:

我们的应用程序都是由这三种类加载器互相配合进行加载的,如果有必要还可以加入自定义的类加载器。

双亲委派模型

前文所说的那些类加载器的关系如下图:

这种层次关系称为类加载器的双亲委派模型,其要求除了顶层的启动类加载器之外,其余的类加载器都要有自己的父类加载器(这种父子关系一般通过组合来实现,而非继承)。

双亲委派模型工作过程是:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时,子加载器才会尝试自己去加载。

为什么需要双亲委派模型

有一个显而易见的好处是 Java 类随着它的类加载器一起具备了一种带有优先级的层次关系。例如java.lang.Object,它存放在 rt.java 中,无论哪一个类加载器要加载这个类,最终都会委派给处于模型最顶端的启动类加载器进行加载,因此 Object 类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型,而是由各个类加载器去自行加载的话,如果用户自己编写了一个 java.lang.Object 类,并放在程序的 ClassPath 中,那么系统中将会出现多个不同的 Object 类,Java 体系中最基础的行为也就无法保证,应用程序会变得一片混乱。

如何实现双亲委派模型

双亲委派模型的原理很简单,实现也简单。每次通过先委托父类加载器加载,当父类加载器无法加载时,再自己加载。其实ClassLoader类默认的loadClass方法已经帮我们写好了,我们无需去写。

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 首先,检查一下指定名称的类是否已经加载过,
		// 如果加载过了,就不需要再加载,直接返回。
        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) {
                // 抛出 ClassNotFoundException 说明父类加载器无法完成加载
				// ignore 这个异常
            }
 
            if (c == null) {
                // 如果父加载器或者启动类加载器都没有找到指定的类
				// 那么调用当前类加载器的 findClass 方法来完成类加载
                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;
    }
}

参考资料

-《深入理解Java虚拟机》 周志明著

License


这是一个不定时更新的、披着程序员外衣的文青小号。

在这里,既分享极客技术,也记录人间烟火,欢迎关注。


Comment