当前位置: 首页 > news >正文

【Tomcat】基础总结:类加载机制

【Tomcat】基础总结:类加载机制

  • 1、Tomcat中有哪些类加载器?
  • 2、Tomcat的类加载机制是怎么样的?
    • 2.1 为什么破坏双亲委派
    • 2.2 如何避免重复加载

1、Tomcat中有哪些类加载器?

Tomcat的类加载机制是指Tomcat在运行时如何加载和管理Java类。Tomcat的类加载机制的实现并没有严格遵守双亲委派原则,而是采用了一种层次化的类加载器结构,这种结构旨在提供更好的隔离性和灵活性,以支持多个Web应用程序的部署和运行。

在这里插入图片描述

默认情况下,Server类加载器和Shared类加载器是未定义的,需要通过在conf/catalina.properties中定义server.loader和/或shared.loader属性的值,才会是这个更复杂的层次结构。

Server类加载器只对Tomcat内部可见,对于Web应用程序完全不可见。

Shared类加载器对所有Web应用程序可见,可以用于在所有Web应用程序之间共享代码。但是,对这些共享代码进行更新将需要重新启动Tomcat。

所以,真正的需要一定有的,并且我们通常需要关注的就是下面这个层级关系:

在这里插入图片描述

启动类加载器(Bootstrap ClassLoader): 负责加载JVM自身的核心类库(如java.lang、java.util等)和JVM相关的类。启动类加载器是JVM的一部分,负责加载JVM运行所需的基础类。主要加载JRE中的lib包及lib/ext包下的内容

系统类加载器(System Class Loader):负载加载Tomcat内部的一些核心类库,这些类一般在$CATALINA_HOME/bin这个目录下, 一般包含bootstarap.jar、tomcat-juli.jar以及common-daemon.jar等。

公共类加载器(Common Class Loader): 负责加载Tomcat的公共类和库,位于$CATALINA_HOME/lib目录下的JAR文件。这些类库是Tomcat启动时加载的,是整个Tomcat实例中共享的类。

Web应用程序类加载器(Webapp Class Loader): 每个Web应用程序都有一个独立的Web应用程序类加载器,负责加载该Web应用程序的类和资源。它从CATALINAHOME/webapps/<webappname>/WEB−INF/classes目录和CATALINA_HOME/webapps/<webapp_name>/WEB-INF/classes目录和CATALINAHOME/webapps/<webappname>/WEBINF/classes目录和CATALINA_HOME/webapps/<webapp_name>/WEB-INF/lib目录加载类和JAR文件。

2、Tomcat的类加载机制是怎么样的?

Tomcat的类加载机制,在默认情况下,是先把当前要加载的类委托给BootstrapClassLoader尝试加载,为了避免JRE中的核心类被我们应用自己的类给覆盖(如String等),Bootstrap如果无法加载,那么就由WebAppClassLoader尝试加载,如果无法加载,那么再委托通过双亲委派的方式向上委派给Common、System等类加载进行加载,即顺序为:Bootstrap->WebApp->System->Common

上面的是默认情况,tomcat中有一个配置delegate,他的默认值是false,如果设置成true了,那么他就会严格遵守双亲委派,按照Bootstrap->System->Common->WebApp的顺序进行加载。

public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {//加锁,防止并发synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {if (log.isDebugEnabled()) {log.debug("loadClass(" + name + ", " + resolve + ")");}Class<?> clazz = null;// ...// 检查本地缓存是否已加载该类,如果是,则直接返回缓存中的 Class 对象。clazz = findLoadedClass0(name);if (clazz != null) {if (log.isDebugEnabled()) {log.debug("  Returning class from cache");}if (resolve) {resolveClass(clazz);}return clazz;}// 检查另一个类加载缓存,如果是GraalVM环境,直接返回缓存中的 Class 对象。clazz = JreCompat.isGraalAvailable() ? null : findLoadedClass(name);if (clazz != null) {if (log.isDebugEnabled()) {log.debug("  Returning class from cache");}if (resolve) {resolveClass(clazz);}return clazz;}/** 尝试使用Bootstrap类加载器加载类,以防止Web应用程序覆盖Java SE类。如果加载成功,则返回加载的 Class 对象。*/String resourceName = binaryNameToPath(name, false);ClassLoader javaseLoader = getJavaseClassLoader();boolean tryLoadingFromJavaseLoader;try {URL url = javaseLoader.getResource(resourceName);tryLoadingFromJavaseLoader = url != null;} catch (Throwable t) {ExceptionUtils.handleThrowable(t);tryLoadingFromJavaseLoader = true;}if (tryLoadingFromJavaseLoader) {try {clazz = javaseLoader.loadClass(name);if (clazz != null) {if (resolve) {resolveClass(clazz);}return clazz;}} catch (ClassNotFoundException e) {// Ignore}}boolean delegateLoad = delegate || filter(name, true);// 根据 delegate 属性和其他条件判断是否应该委派加载给父类加载器。// 如果需要委派,则直接先进行委派if (delegateLoad) {if (log.isDebugEnabled()) {log.debug("  Delegating to parent classloader1 " + parent);}try {clazz = Class.forName(name, false, parent);if (clazz != null) {if (log.isDebugEnabled()) {log.debug("  Loading class from parent");}if (resolve) {resolveClass(clazz);}return clazz;}} catch (ClassNotFoundException e) {// Ignore}}// 自己尝试加载// 能走到这里,肯定是BootStrap没加载到,之后还有两种情况:// 1、如果delegate为ture的话,说明上层类加载器也没记载到。// 2、如果delegate为false,那么就还没有进行过委派,先在这里尝试自己加载。if (log.isDebugEnabled()) {log.debug("  Searching local repositories");}try {clazz = findClass(name);if (clazz != null) {if (log.isDebugEnabled()) {log.debug("  Loading class from local repository");}if (resolve) {resolveClass(clazz);}return clazz;}} catch (ClassNotFoundException e) {// Ignore}// 如果delegate为false,说明还没有做过委派,那么委派给父类加载器加载类。if (!delegateLoad) {if (log.isDebugEnabled()) {log.debug("  Delegating to parent classloader at end: " + parent);}try {clazz = Class.forName(name, false, parent);if (clazz != null) {if (log.isDebugEnabled()) {log.debug("  Loading class from parent");}if (resolve) {resolveClass(clazz);}return clazz;}} catch (ClassNotFoundException e) {// Ignore}}}throw new ClassNotFoundException(name);
}

整个代码的过程就是:

  1. 加锁: 方法使用同步块确保线程安全
  2. 检查已加载类缓存: 首先,通过调用 findLoadedClass0 方法检查本地缓存是否已加载该类,如果是,则直接返回缓存中的 Class 对象。
  3. 检查已加载类缓存(GraalVM 兼容性处理): 通过调用 findLoadedClass 方法检查另一个类加载缓存,如果是GraalVM环境,直接返回缓存中的 Class 对象。
  4. 尝试使用Bootstrap类加载器加载: 尝试使用Bootstrap类加载器加载类,以防止Web应用程序覆盖Java SE类。如果加载成功,则返回加载的 Class 对象。
  5. 决定是否委派加载: 根据 delegate 属性和其他条件判断是否应该委派加载给父类加载器。
  6. 委派给父类加载器: 如果需要委派加载(delegate为true),尝试使用父类加载器加载类。
  7. 自己尝试加载: 如果未指定需要委派(delegate为false),或者未从父类加载器中找到类,则调用 findClass 方法尝试自己进行类加载。
  8. 委派给父类加载器: 如果未指定需要委派(delegate为false),且自己没加载到类,则尝试使用父类加载器加载类。

2.1 为什么破坏双亲委派

一个Tomcat,是可以同时运行多个应用的,而不同的应用可能会同时依赖一些相同的类库,但是他们使用的版本可能是不一样的,但是这些类库中的Class的全路径名因为是一样的,如果都采用双亲委派的机制的话,是无法重复加载同一个类的,那么就会导致版本冲突。

而为了有更好的隔离性,所以在Tomcat中,每个应用都由一个独立的WebappClassLoader进行加载,这样就可以完全隔离开。而多个WebAppClassLoader之间是没有委派关系的,他们就是各自加载各自需要加载的Jar包。

由于每个Web应用程序都有自己的类加载器,因此不同Web应用程序中的类可以使用相同的类名,而不会产生命名冲突。

同时,由于每个Web应用程序都有自己的类加载器,因此在卸载一个Web应用程序时,它的所有类都会从内存中清除,这可以避免内存泄漏的问题。

这种层次化的类加载器结构和委派机制确保了类的唯一性和隔离性,避免了类的重复加载和冲突,同时也实现了多个Web应用程序的隔离和独立运行。

2.2 如何避免重复加载

因为每个应用都是用WebAppClassLoader独自加载的,但是如果有一个公共的jar包,比如Spring,各个应用的版本都一样,那么岂不是要重复加载很多次了?这不是浪费么?

Tomcat给了个方案,那就是SharedClassLoader,我们可以把可以指定一个目录,让SharedClassLoader来加载,他加载的类在各个APP中都是可以共享使用的。

参考链接:
1、https://www.yuque.com/hollis666/wk6won/rgupmyr7wo4s8zi0
2、https://www.yuque.com/hollis666/wk6won/evlwzsa8s6mx93ly

http://www.dtcms.com/a/392941.html

相关文章:

  • 127、【OS】【Nuttx】【周边】效果呈现方案解析:比较浮点数(上)
  • 计网协议簇具体协议
  • 电路分析基础笔记
  • 【JVM 常用工具命令大全】
  • 从iload_1 iload_2 iadd字节码角度看jvm字节码执行
  • openssl 启用AES NI加速对AES加密性能影响的测试
  • LeetCode:32.随机链表的复制
  • 基于SpringBoot+Vue的旅游系统【协同过滤推荐算法+可视化统计】
  • 前端实现一个星空特效的效果(实战+讲解)
  • 【嵌入式】【科普】软件模块设计简介
  • 【ROS2】ROS2通讯机制Topic常用命令行
  • 欧姆龙NJ系列PLC编程标准化案例
  • 【OpenGL】LearnOpenGL学习笔记25 - 法线贴图 NormalMap
  • UE5 基础应用 —— 09 - 行为树 简单使用
  • 客户端实现信道管理
  • 异常解决记录 | Yarn NodeManager 注册异常
  • 【C#】C# 调用 Python 脚本正确姿势:解决 WaitForExit 死锁与退出检测问题
  • Java25新特性
  • 卷积神经网络CNN-part9-DenseNet
  • 深入浅出密码学第一章课后题(持续更新)
  • Mysql 入门概览
  • 大模型中权重共享的作用?
  • 【精品资料鉴赏】55页可编辑PPT详解 数字化高校智慧后勤解决方案
  • LLM大模型 - 实战篇 - AI Agents的开发应用
  • 【分布式技术】RedisShake相关功能详细介绍
  • qsv:一款高性能的CSV数据处理工具
  • `html` 将视频作为背景
  • 口播提词器怎么选?手机提词器实测指南与参数推荐
  • 解剖线性表
  • 计算数学研究方向有哪些细分领域?