双亲委派模型

Java 虚拟机中类加载的过程包括加载、验证、准备、解析、初始化五个阶段,其中验证、准备、解析三个阶段合起来又称为连接阶段。

加载是类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情:

  1. 通过类的全限定名获取其定义的二进制字节流。
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  3. 在 Java 堆中生成一个代表这个类的 java.lang.Class 对象,作为对方法区中这些数据的访问入口。

类加载器

JVM 通过类加载器实现类的加载动作。对于任意一个类,都需要由它的类加载器和这个类本身一同确定其在Java虚拟机中的唯一性。也就是说,即使两个类来源于同一个Class文件,只要加载它们的类加载器不同,那这两个类就必定不相等。

从 Java 虚拟机的角度来讲,存在以下两种类加载器:

  1. 启动类加载器:使用C++实现(仅限于Hotspot),是虚拟机自身的一部分
  2. 其它类加载器:使用 Java 语言实现,全部继承自抽象类java.lang.ClassLoader,这些类加载器需要由启动类加载器加载到内存中之后才能去加载其它类

从 Java 开发人员的角度看,类加载器可以大致划分为以下三类:

  1. 启动(Bootstrap)类加载器:负责加载 \jre\lib 下并且能被虚拟机识别的类库(如rt.jar)。启动类加载器无法被Java程序直接引用
  2. 扩展(Extension)类加载器:负责加载 \jre\lib\ext 中的类库(如javax.*开头的类)。开发者可以直接使用扩展类加载器
  3. 系统(System)类加载器:又称为应用程序类加载器,负责加载 CLASSPATH 环境变量指定的jar包与类路径。用户自定义的类一般由系统类加载器加载

除此之外,我们也可以自定义类加载器,只需要继承 java.lang.ClassLoader 并覆盖 findClass(String name) 方法即可。

可以通过如下代码获取类加载器的基本信息:

public class test {
    public static void main(String[] args) {
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        System.out.println(classLoader);
        System.out.println(classLoader.getParent());
        System.out.println(classLoader.getParent().getParent());
    }
}

输出结果:

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@548c4f57
null

从结果可以看到,系统类加载器的父加载器是扩展类加载器,试图获取扩展类加载器的父类加载器时返回了null,因为启动类加载器无法被Java程序直接引用。


双亲委派模型

JVM在加载类时默认采用的是双亲委派模型。当某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,直至启动类加载器。如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,自己才尝试去加载。

这种层次关系称为类加载器的双亲委派模型。我们把每一层上面的类加载器叫做当前层类加载器的父加载器,它们之间的父子关系不是通过继承关系实现,而是使用组合关系复用父加载器中的代码。

双亲委派模型并不是一个强制性的约束模型,而是 Java 设计者们推荐给开发者的一种类的加载器实现方式。

使用双亲委派模型来组织类加载器之间的关系,很明显的好处就是 Java 类随着它的类加载器一起具备了一种带有优先级的层次关系,这对于保证Java程序的稳定运作很重要。

例如,类 java.lang.Object 类存放在 \jre\lib 下的rt.jar之中,无论哪个类加载器加载此类,最终都会委派给启动类加载器进行加载,保证了 Object 类在程序中的各种类加载器中都是同一个类。因此,如果开发者尝试编写一个与rt.jar类库中重名的Java类,可以正常编译,但是永远无法被加载运行。


双亲委派模型的系统实现

除了启动类加载器,其它加载器都继承了ClassLoader抽象类,其中有一个loadClass()方法。

该方法会先检测该类有没有被加载过,没有则调用父类加载器进行加载;如果没有父类加载器,调用启动类加载器进行加载。如果都没有加载成功,调用自己的findClass()方法进行加载。

如下所示:

protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
    // First, check if the class has already been loaded
        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 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;
    }
}

参考资料

  • 深入理解Java虚拟机:JVM高级特性与最佳实践(第二版)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值