跳到主要内容

05、JVM 实战 - 类的加载篇,双亲委派机制

一、定义

定义

如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回。只有父类加载器无法完成此加载任务时,才自己去加载。

工作原理

1、 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;;
2、 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器;;
3、 如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式;

 

二、如何证明、源码分析

证明

双亲委派机制在java.lang.ClassLoader.loadClass(String name, boolean resolve)接口中体现。该接口的逻辑如下:

1、 先在当前加载器的缓存中查找有无目标类,如果有,直接返回;
2、 判断当前加载器的父加载器是否为空,如果不为空,则调用parent.loadClass(name,false)接口进行加载;
3、 反之,如果当前加载器的父类加载器为空,则调用findBootstrapClassOrNull(name)接口,让引导类加载器进行加载;
4、 如果通过以上3条路径都没能成功加载,则调用findClass(name)接口进行加载该接口最终会调用;

java.lang.ClassLoader接口的defineClass系列的native接口加载目标Java类。 双亲委派的模型就隐藏在这第2和第3步中。

举例

假设当前加载的是java.lang.Object这个类,很显然,该类属于JDK中核心得不能再核心的一个类,因此一定只能由引导类加载器进行加载。当JVM准备加载java.lang.Object时,JVM默认会使用系统类加载器去加载,按照上面4步加载的逻辑,在第1步从系统类的缓存中肯定查找不到该类,于是进入第2步。由于从系统类加载器的父加载器是扩展类加载。

思考

如果在自定义的类加载器中重写java.lang.ClassLoader.loadClass(String)或 java.lang.ClassLoader.loadClass(String, boolean)方法,抹去其中的双亲委派机制,仅保留上面这4步中的第1步与第4步,那么是不是就能够加载核心类库了呢?

这也不行!因为JDK还为核心类库提供了一层保护机制。不管是自定义的类加载器,还是系统类加载器抑或扩展类加载器,最终都必须调用java.lang.ClassLoader.defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain)方法,而该方法会执**行preDefineClass(String name, ProtectionDomain pd)**接口,该接口中提供了对JDK核心类库的保护。

三、优势、劣势

双亲委派机制优势

1、 避免类的重复加载,确保一个类的全局唯一性;

Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载 ,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。

2、 保护程序安全,防止核心API被随意篡改;

双亲委托模式的劣势

检查类是否加载的委托过程是单向的,这个方式虽然从结构上说比较清晰,使各个ClassLoader的职责非常明确,但是同时会带来一个问题,即顶层的ClassLoader无法访问底层的ClassLoader所加载的类。

四、破坏双亲委派机制及举例

为什么要破坏双亲委派机制?

为了解决顶层的ClassLoader无法访问底层的ClassLoader所加载的类的问题。

第一次破坏双亲委派机制

JDK1.2之后。

第二次破坏双亲委派机制

线程上下文类加载器(Thread ContextClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。

第三次破坏双亲委派机制

第三次“被破坏”是由于用户对程序动态性的追求而导致的。如:代码热替换(Hot Swap)、模块热部署(Hot Deployment)等

1、热替换的实现

热替换是指在程序的运行过程中,不停止服务,只通过替换程序文件来修改程序的行为。热替换的关键需求在于服务不能中断,修改必须立即表现正在运行的系统之中。基本上大部分脚本语言都是天生支持热替换的,比如:PHP,只要替换了PHP源文件,这种改动就会立即生效,而无需重启Web服务器。

但对Java来说,热替换并非天生就支持,**如果一个类已经加载到系统中,通过修改类文件,并无法让系统再来加载并重定义这个类。**因此,在Java中实现这一功能的一个可行的方法就是灵活运用ClassLoader。

注意:由不同ClassLoader加载的同名类属于不同的类型,不能相互转换和兼容。即两个不同的ClassLoader加载同一个类,在虚拟机内部,会认为这2个类是完全不同的。

根据这个特点,可以用来模拟热替换的实现,基本思路如下图所示:

 

2、Tomcat的类的加载机制