目录
6.5. 类加载器-双亲委派机制
6.5.1. 双亲委派机制-作用
6.5.2. 双亲委派机制-工作流程
6.5.3. 双亲委派机制-父加载器
6.5.4. 双亲委派机制-面试题
6.5.5. 双亲委派机制-代码主动加载一个类
6.6. 类加载器-打破双亲委派机制
6.6.1. 打破委派-ClassLoader原理
6.6.2. 打破委派-打破的场景
6.6.3. 打破委派-自定义类加载器
6.6.4. 打破委派-线程上下文类加载器
6.6.5. 打破委派-OSGi模块化
6.7. 类加载器-JDK8前后的类加载器
6.5. 类加载器-双亲委派机制
6.5.1. 双亲委派机制-作用
- 双亲委派机制的作用:解决谁来加载类的问题
-
- 保证类加载的安全性:避免恶意代码替换JDK中的核心类库(比如:java.lang.String),确保核心类库的完整性和安全性
- 避免类的重复加载:避免一个类被反复加载
6.5.2. 双亲委派机制-工作流程
- 双亲委派机制的工作流程:当类加载器接收到加载类的任务时,会自底向上查找父类加载器是否加载过该类,是则结束这个过程,否则自顶向下进行加载该类
-
- 自底向上工作流程:每个类加载器都有父类加载器,在类加载过程中,每个类加载器会检查自身是否加载了该类,是则直接返回该类,否则将加载请求委派给父类加载器
- 自底向上工作优点:只要一个类加载器加载过该类就会直接返回,避免了重复加载
- 自顶向下工作流程:父类加载器会检查当前类是否在自己的加载目录中,是则加载后返回类对象,否则交给子类去加载
- 自顶向下的优点:实现从上到下的加载优先级
6.5.3. 双亲委派机制-父加载器
- 父加载器辨析:每个Java实现的类加载器中保存了一个成员变量叫"父"(parent)类加载器,可以理解为它的上级,但不是继承关系
-
- 应用程序类加载器的父类加载器:扩展类加载器
- 扩展类加载器的父类加载器:null,启动类加载器是JVM源码中的,Java代码是获取不到的,但是代码逻辑上启动类加载器的父类加载器还是启动类加载器
- 启动类加载器的父类加载器:启动类加载器是用C++编写的没有父类加载器
6.5.4. 双亲委派机制-面试题
-
- 启动类加载器加载,根据双亲委派机制,它的优先级是最高的
-
- 不能,因为启动类加载器在程序启动时已经加载了JDK提供的String类,接收到委派请求时会直接返回加载好的String类
6.5.5. 双亲委派机制-代码主动加载一个类
- 使用Java代码可以主动加载一个类,有两种实现方式:
-
- 调用Class.forName方法使当前类的类加载器去加载该类
- 调用getClassLoader方法获取当前类的类加载器,再用类加载器的loadClass方法让该类加载器加载指定的方法
6.6. 类加载器-打破双亲委派机制
6.6.1. 打破委派-ClassLoader原理
- ClassLoader的原理就存在于四个核心方法中
- loadClass:
-
- 作用:类加载的入口,内部实现双亲委派机制,内部会调用findClass
- 代码逻辑:
-
- 使用synchronized加锁,防止多线程情况下出现类被多次加载
- 使用findLoadClass方法判断这个类是否被当前类加载器加载过
-
-
- 未被加载过,则判断父类加载器是否为空
-
-
-
-
- 不为空,则由父类加载器再判断是否加载过这个类,依次类推
- 为空,则由启动类加载器去加载
-
-
-
-
- 已被加载过,则直接返回这个类
-
-
- 执行完向上委派动作,但类任未被加载,则由当前类加载器加载
-
-
- 使用URLClassLoader的findClass方法获取特定目录下的Class字节码文件,获取文件对象
-
-
- 调用重载方法传入resolve=false,不执行连接的过程
- findClass:由类加载器子类实现,获取二进制数据时调用defineClass,比如URLClassLoader会根据文件路径去获取类文件的二进制数据
- defineClass:对类名进行校验,调用JVM方法将字节码信息加载到JVM运行时数据区
- resolveClass:执行类生命周期中的连接阶段
6.6.2. 打破委派-打破的场景
- Tomcat打破双亲委派机制的原因:Tomcat程序中运行多个Web应用时,如果Web应用中出现相同限定名的类,Tomcat要保证这些类能够正常加载并运行且保证他们是不同的类,Tomcat就需要打破双亲委派机制
- Tomcat打破双亲委派机制的方式:Tomcat为每一个Web应用提供了自定义类加载器来加载对应的类,这样就实现了应用之间的类的隔离
6.6.3. 打破委派-自定义类加载器
- 自定义类加载器打破双亲委派机制的方法:复写ClassLoader中的loadClass方法
- 常见问题:
-
- 要加载的类名如果是以java.开头,则会抛出安全性异常
- 加载自定义的类都会有一个共同
的父类Object,需要在代码中交由父类加载器去加载 - 自定义类加载器不手动指定parent会默认指定应用类加载
- 两个自定义类加载器加载同一个类会被认为是两个对象,只有相同的类加载器+想通的类限定名才会被认为是一个对象
6.6.4. 打破委派-线程上下文类加载器
- 线程上下文类加载器应用场景:JDBC(为例),JNDI
- JDBC概述:JDBC提供DriverManager来管理jar包中引入的数据库驱动,这样就能在Java中操作不同的数据库
- JDBC打破委派困境:DriverManager位于rt.jar包中,由启动类加载器加载,但依赖中的MySQL驱动实现类需要应用类加载器去加载
- JDBC-SPI机制:SPI(Service Provider Interface)JDK内置服务发现机制,通过SPI快速找到Driver接口的实现类,类似Spring中的依赖注入
- JDBC工作原理:MySQL为例
-
- 启动类加载器加载位于rt.jar包的DriverManager
- 初始化DriverManager时,通过SPI机制加载jar包中的数据库驱动实现类,将此实现类注册到DriverManager中交由他管理
-
-
- DriverManager利用SPI机制会去加载META-INF/services路径下的java.sql.Driver文件,将MySQL实现了Driver接口的驱动实现类的全限定名写入文件就可以被加载并管理
-
-
- SPI中利用了线程上下文类加载器(一般是应用类加载器)去加载获取的Driver驱动类并创建对象
- SPI中利用了线程上下文类加载器(一般是应用类加载器)去加载获取的Driver驱动类并创建对象
-
-
- DriverManager使用ServiceLoader去加载Driver实现类,ServiceLoader中获取线程上下文类加载器去加载实现类
-
-
- 这种由启动类加载器加载的类去委派应用程序类加载器去加载类的方式打破了双亲委派机制
- 但是JDBC案例中都使用的是JDK提供的类加载器还是会走双亲委派流程,并没有重写loadClass,也可以说没有打破双亲委派机制
6.6.5. 打破委派-OSGi模块化
- OSGi作用:OSGi是模块化框架,解决早起JDK所有核心类都放在rt.包下难以管理的问题,它实现同级之间的类加载器委托加载.还使用类加载器实现了热部署功能
6.7. 类加载器-JDK8前后的类加载器
- JDK8之前版本:扩展类加载器和应用程序类加载器的源码都位于rt.jar包下的sun.misc.Launcher.java中,这两个加载器都实现了URLClassLoader,也可以说JDK8之前类加载器是按照类的位置去加载的
- JDK8之后版本:JDK9引入了module概念,类不在放在jar包中加载,而是放在一个个jmod文件中,从jmod文件中加载文件,类加载器发生很多变化:
-
- 启动类加载器由Java编写,位于jdk.internal.loader.ClassLoader类中
- 启动类加载器由Java编写,位于jdk.internal.loader.ClassLoader类中
-
-
- Java中的BootClassLoader继承自BuiltinClassLoader实现从模块中找到要加载的字节码文件
- 启动类加载器依然无法通过Java代码找到保持了统一
-
-
- 扩展类加载器被替换成了平台类加载器(Platform Class Loader)
- 扩展类加载器被替换成了平台类加载器(Platform Class Loader)
-
-
- 平台类加载器遵循模块化方式加载字节码文件,继承关系从URLClassLoader变为BuiltinClassLoader,BuiltinClassLoader实现了从模块中加载字节码文件,平台类加载器的存在更多是为老板的设计方案兼容,自己没有特殊逻辑
-