AnthonyZero's Bolg

JVM-类加载器及双亲委派模型

类加载器

在JVM中,一个类被加载到虚拟机这个过程包括有3个步骤,即加载、连接和初始化。而加载这个过程,就是由类加载器ClassLoader进行加载的,类加载器天生就负责这个职责

类加载器负责加载程序中的类(类和接口),并赋予唯一的名字予以标识

  1. 启动类加载器Bootstrap ClassLoader
    最顶层的加载类,由C++实现,负责加载 %JAVA_HOME%/lib目录下的jar包和类或者被 -Xbootclasspath参数指定的路径中的所有类。

  2. 扩展类加载器Extension ClassLoader
    主要负责加载目录 %JRE_HOME%/lib/ext 目录下的jar包和类,或被 java.ext.dirs 系统变量所指定的路径下的jar包。

  3. 应用类加载器App ClassLoader
    面向我们用户的加载器,负责加载当前应用classpath下的所有jar包和类,它也是Java程序默认的类加载器。

两个类相等,需要类本身相等,并且使用同一个类加载器进行加载。这是因为每一个类加载器都拥有一个独立的类名称空间。这里的相等,包括类的 Class 对象的 equals() 方法、isAssignableFrom() 方法、isInstance() 方法的返回结果为 true,也包括使用 instanceof 关键字做对象所属关系判定结果为 true

双亲委托机制

核心:其一,自底向上检查类是否已加载;其二,自顶向下尝试加载类
classloader2.png

介绍
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成(此类没被加载过),每个层次的类加载器都是如此,因此最后所有的请求都会传递到顶层的启动类加载器中,只有当父加载器返回自己无法完成这个加载请求(即它的搜索范围内没有找到所需要的类),子加载器才会尝试去自己加载。(这是一个由上往下的过程)

示例:每个类加载都有一个父类加载器

1
2
3
4
5
6
7
System.out.println(DateUtils.class.getClassLoader());
System.out.println(DateUtils.class.getClassLoader().getParent());
System.out.println(DateUtils.class.getClassLoader().getParent().getParent());

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1a86f2f1
null

AppClassLoader的父类加载器为ExtClassLoader ExtClassLoader的父类加载器为null,null并不代表ExtClassLoader没有父类加载器,而是 BootstrapClassLoader

双亲委派模型的好处:
双亲委派模型保证了Java程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,系统就会出现多个不同的 Object 类。

如果我们不想用双亲委派模型怎么办?
为了避免双亲委托机制,我们可以自己定义一个类加载器,然后重载 loadClass() 即可。