1. 类加载器的分类
Bootstrap ClassLoader是JVM内部定义的,没有办法被引用
2. 关于类加载器的一些疑惑?
①不同的类加载器,除了读取二进制流的动作和范围不一样,后续的加载逻辑是否也不一样?
②遇到限定名一样的类,这么多类加载器会不会产生混乱?
注意:JVM中每个类加载器都有属于自己的命名空间
解决上面的问题的方式就是使用双亲委派机制!
除了读取二进制流以外,其他的操作都由JVM内部操作
3. 什么是双亲委派机制?
双亲委派: 一个java类加载进JVM内存的过程
1 每个类加载器对他加载过的类都有一个缓存。
2 向上委托查找,向下委托加载。
工作原理
(1)如果一个类加载器收到了类加载请求,它并不会自己先加载,而是把这个请求委托给父类的加载器去执行
(2)如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的引导类加载器;
(3)如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成加载任务,子加载器才会尝试自己去加载,这就是双亲委派机制
(4)父类加载器一层一层往下分配任务,如果子类加载器能加载,则加载此类,如果将加载任务分配至系统类加载器也无法加载此类,则抛出异常
双亲委派机制优势
通过上面的例子,我们可以知道,双亲机制可以
- 避免类的重复加载
- 保护程序安全,防止核心API被随意篡改
- 自定义类:java.lang.String (没用)
- 自定义类:java.lang.ShkStart(报错:阻止创建 java.lang开头的类)
4. 什么时候不使用双亲委派机制?
双亲委派模型很好的解决了各个类加载器的基础类统一问题(越基础的类由越上层的类加载器加载),基础类之所以称为基础,是因为它们总是作为被用户调用的API,但如果基础类又要回调用户的代码,那该怎么办呢。
假定类加载器 A 作为 B 的 parent,A 加载的类 对 B 是可见的; 然而 B 加载的类 对 A 却是不可见的。这是由 classloader 加载模型中的可见性(visibility)决定的。可见性原则允许子类加载器查看父ClassLoader加载的所有类,但父类加载器看不到子类加载器的类。
双亲委派模型不适用场景:
常见的 SPI 有 JDBC、JNDI、JAXP 等,这些 SPI 的接口由核心类库提供,却由第三方实现,这样就存在一个问题:
SPI 的接口是 Java 核心库的一部分,由 BootstrapClassLoader 加载;并且接口的调用逻辑也是在核心库
SPI 实现的 Java 类一般是由 AppClassLoader 来加载的。
根据ClassLoader加载模型中的可见性原则,BootstrapClassLoader 是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能将加载任务委派给 AppClassLoader,因为它是最顶层的类加载器。也就是说,双亲委派模型并不能解决这个问题。
破坏双亲委派模型的典型示例:
JNDI服务: JNDI的目的就是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的ClassPath下的JNDI接口提供者(SPI service provider interface)的代码,但启动类加载器不可能认识这些代码
SPI 接口如 java.sql.DriverManager, java.sql.DriverManager 通过扫包的方式拿到指定的实现类,完成 DriverManager的初始化。问题来了,根据双亲委派的可见性原则,启动类加载器加载的 DriverManager 是不可能拿到系统应用类加载器加载的实现类
解决方案:
java设计团队引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。
这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器AppClassLoader
Java 在核心类库中定义了许多接口,并且还给出了针对这些接口的调用逻辑,然而并未给出实现。开发者要做的就是定制一个实现类,在 META-INF/services 中注册实现类信息,以供核心类库使用。
在核心类库使用 SPI 接口时,传递的类加载器使用线程上下文类加载器,就可以成功的加载到 SPI 实现的类(被线程上下文类加载器加载的类,能够被核心类库的调用方调用,且实现类不是通过双亲委派模型的方式加载,而是直接用传递的线程上下文类加载器加载)
SPI ServiceLoader 通过线程上下文获取能够加载实现类的classloader,一般情况下是 application classloader,绕过了双亲委派模型的层级限制,逻辑上打破了双亲委派原则
5. 关于JDK的类加载对象
注意类java的类加载机制并不是继承关系,只是有双亲指向,但是不存在父子关系!
而jdk的类加载对象存在这个关系,也就是说存在继承与实现关系!
ClassLoader -> SecureClassLoader ->URLClassLoader -> ExtClassLoader ,AppClassLoader