Java类加载器的迷宫——双亲委派模型的来龙去脉

简介: Java类加载器是JVM中最神秘也最强大的组件之一。它负责将类的字节码加载到JVM中,转换为java.lang.Class实例。

Java类加载器是JVM中最神秘也最强大的组件之一。它负责将类的字节码加载到JVM中,转换为java.lang.Class实例。类加载器的层次结构和双亲委派模型是Java平台安全性和稳定性的基石,也是实现热部署、模块隔离和应用服务器的关键技术。
参考:https://amwtm.cn/category/kitchen.html

类加载的基本过程包括:加载(查找并加载类的字节码)、验证(验证字节码的正确性)、准备(为静态变量分配内存并设置默认值)、解析(将符号引用转换为直接引用)、以及初始化(执行静态初始化块和静态变量赋值)。其中,加载阶段由类加载器完成。

类加载器的层次结构从JVM启动时就已建立。启动类加载器(Bootstrap ClassLoader)是最顶层的加载器,由C++实现,负责加载JAVA_HOME/lib目录下的核心类库(如rt.jar)。启动类加载器没有父加载器,是类加载器层次结构的根。扩展类加载器(Extension ClassLoader)负责加载JAVA_HOME/lib/ext目录下的扩展类库,其父加载器是启动类加载器。应用类加载器(Application ClassLoader)负责加载应用程序类路径(Classpath)上的类,其父加载器是扩展类加载器。开发者还可以自定义类加载器,继承java.lang.ClassLoader。

双亲委派模型是类加载器的核心工作原则。当一个类加载器收到类加载请求时,它首先将请求委派给父加载器处理。只有当父加载器无法加载该类时,当前加载器才会尝试自己加载。这种模型的优点显而易见:避免了类的重复加载(同一个类在整个JVM中只有一份);保证了核心类库的安全性(用户自定义的java.lang.String不会被加载,因为启动类加载器已经加载了官方版本)。

双亲委派模型的实现位于ClassLoader.loadClass方法中。典型的实现逻辑是:先检查类是否已被加载;如果未加载,调用父加载器的loadClass;如果父加载器加载失败(抛出ClassNotFoundException),则调用自己的findClass。自定义类加载器通常只需要重写findClass方法,而不是loadClass,以保持双亲委派模型。
参考:https://amwtm.cn/category/bedroom.html

破坏双亲委派模型的场景虽然罕见,但确实存在。最著名的例子是JNDI(Java Naming and Directory Interface)和JDBC。JNDI的核心类在rt.jar中(由启动类加载器加载),但它的实现类(如JDBC驱动)在Classpath上(由应用类加载器加载)。启动类加载器无法委派给应用类加载器,因为它不知道应用类加载器的存在。解决方案是引入线程上下文类加载器——JNDI通过Thread.currentThread().getContextClassLoader()获取应用类加载器,从而加载实现类。Tomcat等Web服务器也破坏了双亲委派模型,以实现Web应用之间的类隔离。

线程上下文类加载器是Java中一个容易被忽视但非常重要的机制。每个线程可以关联一个类加载器,通过setContextClassLoader设置。框架代码可以使用线程上下文类加载器加载调用者的类,从而突破双亲委派的限制。许多Java EE规范(如JPA、JAXB)都依赖线程上下文类加载器。

自定义类加载器的应用场景包括:从非标准位置加载类(如网络、数据库、加密文件);实现类版本管理(同一应用中使用不同版本的同一个库);实现热部署(重新加载修改后的类);以及实现字节码增强(如AOP框架)。Tomcat的WebappClassLoader、OSGi的Bundle类加载器、以及JRebel的类加载器都是自定义类加载器的典型例子。

类加载器与模块化:JPMS(JDK 9)的模块化系统引入了新的类加载器架构。模块路径上的模块由应用类加载器加载,但模块之间的强封装限制了类的可见性。JPMS也引入了Layer概念,允许在同一个JVM中运行多个模块图。java.lang.ModuleLayer可以有自己的类加载器,实现更细粒度的隔离。

类加载器与内存泄漏是生产环境中的常见问题。如果一个类加载器无法被GC回收,它加载的所有类以及这些类的静态变量引用的对象都无法回收。常见的内存泄漏场景包括:将自定义类加载器存储在ThreadLocal中;在自定义类加载器中启动的线程没有正确终止;以及使用JDBC驱动没有正确注销。诊断类加载器泄漏需要使用Heap Dump分析工具(如MAT、VisualVM)。
参考:https://amwtm.cn/category/living-room.html

类加载器与OSGi:OSGi框架实现了比JVM更复杂的类加载器架构。每个Bundle有自己的类加载器,Bundle之间通过导出的包和导入的包建立依赖关系。OSGi的类加载器使用“动态委派”而非简单的双亲委派——Bundle类加载器首先尝试从导入的包中加载,然后从Bundle自身的类路径加载,最后委派给父加载器。OSGi还支持类加载器的动态安装和卸载,实现了模块的热插拔。

类加载器的可见性问题:子加载器加载的类对父加载器不可见。这意味着如果你在自定义类加载器中加载了类A,然后试图在应用类加载器中强制转换A的对象,会抛出ClassCastException,因为两个类加载器加载的同一类被视为不同的类型。这个问题在应用服务器中尤为常见——如果你的代码在Web应用的类加载器上下文中,而库在容器的类加载器中,需要谨慎处理类型转换。

类加载器与JVM规范:JVM规范允许不同类加载器加载同一个类,只要类的全限定名相同,在JVM中就被视为不同的类。这为类隔离提供了基础,但也带来了类型检查的复杂性。instanceof、cast等操作在跨类加载器时需要特别注意。

调试类加载问题:使用-verbose:class可以打印类加载日志;使用ClassLoader.getResourceAsStream可以检查资源的加载位置;使用Thread.currentThread().getContextClassLoader()可以确认当前上下文类加载器。对于复杂的问题,可以使用JVM的-Xbootclasspath/a将类添加到启动类加载器的路径中。

类加载器是Java平台的基石,也是许多高级特性的基础。理解类加载器的工作原理,不仅有助于解决ClassNotFoundException和NoClassDefFoundError,还能帮助开发者利用类加载器实现动态加载、模块化和热部署。虽然JPMS的引入在一定程度上简化了类加载,但类加载器仍然是Java开发者工具箱中的重要工具。
参考:https://amwtm.cn

目录
相关文章
|
存储 缓存 文件存储
如何保证分布式文件系统的数据一致性
分布式文件系统需要向上层应用提供透明的客户端缓存,从而缓解网络延时现象,更好地支持客户端性能水平扩展,同时也降低对文件服务器的访问压力。当考虑客户端缓存的时候,由于在客户端上引入了多个本地数据副本(Replica),就相应地需要提供客户端对数据访问的全局数据一致性。
32698 79
如何保证分布式文件系统的数据一致性
|
前端开发 容器
HTML5+CSS3前端入门教程---从0开始通过一个商城实例手把手教你学习PC端和移动端页面开发第8章FlexBox布局(上)
HTML5+CSS3前端入门教程---从0开始通过一个商城实例手把手教你学习PC端和移动端页面开发第8章FlexBox布局
17753 20
|
设计模式 存储 监控
设计模式(C++版)
看懂UML类图和时序图30分钟学会UML类图设计原则单一职责原则定义:单一职责原则,所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变,那么这个类就具有多于一个的职责。而单一职责原则就是指一个类或者模块应该有且只有一个改变的原因。bad case:IPhone类承担了协议管理(Dial、HangUp)、数据传送(Chat)。good case:里式替换原则定义:里氏代换原则(Liskov 
36684 19
设计模式(C++版)
|
存储 编译器 C语言
抽丝剥茧C语言(初阶 下)(下)
抽丝剥茧C语言(初阶 下)
|
机器学习/深度学习 人工智能 自然语言处理
带你简单了解Chatgpt背后的秘密:大语言模型所需要条件(数据算法算力)以及其当前阶段的缺点局限性
带你简单了解Chatgpt背后的秘密:大语言模型所需要条件(数据算法算力)以及其当前阶段的缺点局限性
24758 14
|
机器学习/深度学习 弹性计算 监控
重生之---我测阿里云U1实例(通用算力型)
阿里云产品全线降价的一力作,2023年4月阿里云推出新款通用算力型ECS云服务器Universal实例,该款服务器的真实表现如何?让我先测为敬!
36662 15
重生之---我测阿里云U1实例(通用算力型)
|
SQL 存储 弹性计算
Redis性能高30%,阿里云倚天ECS性能摸底和迁移实践
Redis在倚天ECS环境下与同规格的基于 x86 的 ECS 实例相比,Redis 部署在基于 Yitian 710 的 ECS 上可获得高达 30% 的吞吐量优势。成本方面基于倚天710的G8y实例售价比G7实例低23%,总性价比提高50%;按照相同算法,相对G8a,性价比为1.4倍左右。
|
存储 算法 Java
【分布式技术专题】「分布式技术架构」手把手教你如何开发一个属于自己的限流器RateLimiter功能服务
随着互联网的快速发展,越来越多的应用程序需要处理大量的请求。如果没有限制,这些请求可能会导致应用程序崩溃或变得不可用。因此,限流器是一种非常重要的技术,可以帮助应用程序控制请求的数量和速率,以保持稳定和可靠的运行。
29838 52

热门文章

最新文章

下一篇
开通oss服务