JAVA基础 - CLASSLOADER双亲委派机制?

类的生命周期

在JAVA中数据类型分为基本数据类型和引用数据类型。基本数据类型,由虚拟机预先定义,引用数据类型则需要进行类加载。
JAVA将引用数据类型分为:类、接口、数组和泛型参数,而「泛型参数」在编译时期会被擦除, 因此JAVA虚拟机的类型实际上只有三种。「数组」是由JAVA虚拟机直接生成的;类、接口则有对应的「字节流」,这此不同形式的「字节流」都会被加载到JAVA虚拟机中。
按照JVM规范,从.CLASS文件到加载到内存中,到类的卸载出内存为止,它的整个生命周期包括7个阶段:
(1)“加载、验证、准备、初始化和卸载” 这五个阶段的顺序是确定的,“解析”可以在初始化之后再开始(运行时绑定或动态绑定或晚期绑定)。
(2)“验证、准备和解析”这三个阶段统称为连接(Linking)。
(2) 类加载过程分为:加载、验证、准备、解析和初始化五个阶段。
在这里插入图片描述

类加载过程

类加载过程分为:加载、验证、准备、解析和初始化五个阶段。这里的「加载」是指「类加载过程」的第一个阶段,在加载阶段,虚拟机需要完成以下 3 件事:
(1)通过一个类的全限定名来获取定义此类的二进制字节流, 使用的就是双亲委派机制
(2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
(3)在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。

类生命周期的其他阶段,不在该博文中介绍…

双亲委派机制是什么

类加载器查找CLASS所采用的是双亲委托模式,由向上委派和向下委派组成了双亲委派。
所谓双亲委托模式就是:首先判断该CLASS是否已经加载,如果没有被加载,不是自身去查找而是委托给父加载器进行查找,然后样依次进行递归,直到委托到最顶层的Bootstrap ClassLoader,如果Bootstrap ClassLoader找到了该CLASS,则直接返回,如果没找到,则继续依次向下查找,最后会交由自身去查找。

双亲委托机制的加载流程

(1)如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。
(2)每一个层次的类加载器都是按照(1)进行递归,所以所有的加载请求最终都应该传送到顶层的启动类加载器中。
(3)只有当父加载器反馈自己无法完成这个加载请求时(搜索范围中没有找到所需的类),子加载器才会尝试自己去加载,如果子类也无法完成,则会依次往下传递,直到这个请求被成功加载,但是一直到自定义加载器都没有找到,JVM就会抛出ClassNotFund的异常。
在这里插入图片描述

一句话:先从下到上,再从上到下(或者说:向上委派,向下加载)

在这里插入图片描述

双亲委派机制的优点

简言之:保证类的唯一性和安全性

(1)避免类的重复加载
如果一个CLASS已经加载过了,就不会再次被加载,而是先从缓存中直接读取;
(2)保护程序安全
防止核心API被随意篡改。通过委托方式不会去篡改核心.CLASS,即使篡改也不会去加载,即使加载了也不再是同一个.CLASS对象了。不同的加载器加载同一个.CLASS也不是同一个CLASS对象。当自己程序中定义了一个和Java.lang包同名的类,因为使用的是双亲委派机制,会由启动类加载器去加载JAVA_HOME/lib中的类,而不是加载用户自定义的类。所以程序可以正常编译,但是自己定义的类无法被加载运行。

### Java 自定义类加载器打破双亲委派机制的实现方式 #### 背景介绍 Java 类加载器采用的是双亲委派模型,即当一个类加载器收到类加载请求时,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。只有当父类加载器无法找到或加载该类时,子类加载器才会尝试自行加载。 要打破这种双亲委派机制,可以通过自定义类加载器并重写 `loadClass` 方法来实现[^1]。 --- #### 核心方法解析 在 Java 中,`java.lang.ClassLoader` 是所有类加载器的基类。其核心方法如下: 1. **`protected Class<?> loadClass(String name, boolean resolve)`** - 这是实现双亲委派的核心方法,在默认情况下会调用父类加载器进行类加载操作。 2. **`protected Class<?> findClass(String name)`** - 默认是一个空方法,用于由开发者提供具体的类加载逻辑。如果希望完全绕过双亲委派,则可以不依赖于 `loadClass` 的默认行为而直接通过此方法加载类[^1]。 为了打破双亲委派机制,可以选择以下两种主要途径之一: --- #### 方式一:重写 `loadClass` 方法 通过覆盖 `loadClass` 方法改变原有的双亲委派流程。以下是具体代码示例: ```java public class CustomClassLoader extends ClassLoader { @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // 不调用父类的loadClass方法,从而跳过双亲委派 try { String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class"; InputStream inputStream = getClass().getResourceAsStream(fileName); if (inputStream == null) { throw new ClassNotFoundException(); } byte[] bytes = inputStream.readAllBytes(); // 将字节码读取到内存中 return defineClass(name, bytes, 0, bytes.length); // 定义类 } catch (IOException e) { throw new ClassNotFoundException(e.getMessage()); } } } ``` 在这个例子中,我们手动处理了 `.class` 文件的加载过程,并未调用父类的 `loadClass` 方法,因此打破了双亲委派机制[^2]。 --- #### 方式二:仅重写 `findClass` 方法 另一种更常见的做法是让 `loadClass` 继续遵循双亲委派原则,但在必要时通过重写的 `findClass` 来接管特定类的加载工作。这种方式更加灵活且安全。 下面展示了一个简单的案例: ```java public class MyCustomClassLoader extends ClassLoader { private final String path; public MyCustomClassLoader(String path) { this.path = path; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { String filePath = path + "/" + name.replace('.', '/') + ".class"; // 构造文件路径 FileInputStream fis = new FileInputStream(filePath); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int data; while ((data = fis.read()) != -1) { // 将 .class 文件内容读入流中 baos.write(data); } byte[] classData = baos.toByteArray(); // 获取字节数组形式的类数据 return defineClass(name, classData, 0, classData.length); // 使用defineClass定义新类 } catch (Exception ex) { throw new ClassNotFoundException(ex.getMessage(), ex); } } } ``` 在此种情况之下,虽然仍然保留了部分双亲委派特性(因为没有修改 `loadClass`),但对于某些特殊需求下的类加载则交给了我们的定制化逻辑执行[^3]。 需要注意的一点是,如果你试图加载属于受保护包空间内的类(如`java.*`),将会抛出异常: ```plaintext java.lang.SecurityException: Prohibited package name: java.lang ``` 这是由于 JVM 对这些敏感区域进行了严格的安全控制所致[^3]。 --- #### 注意事项 - 如果多个自定义类加载器分别加载同一个全限定名的类实例,它们会被视为不同的类型对象,彼此之间不可互换使用[^2]。 - 在实际开发过程中应谨慎考虑是否真的有必要破坏双亲委派模式,因为它可能会引发意想不到的问题或者兼容性隐患。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cloneme01

谢谢您的支持与鼓励!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值