Appearance
Tomcat 中为什么要使用自定义类加载器
Tomcat 是一个广泛使用的 Java Servlet 容器,它采用了自定义类加载器架构而不是简单使用 Java 默认的类加载机制。这种设计有着深思熟虑的原因和明显的优势。
Tomcat 的类加载器层次结构
Tomcat 使用了一个层次化的类加载器结构:
- Bootstrap 类加载器:加载 JVM 运行所需的核心类
- 扩展类加载器:加载 Java 扩展类
- 系统类加载器:加载 Tomcat 自身的类
- Common 类加载器:加载 Tomcat 和所有 Web 应用共享的类
- Catalina 类加载器:加载 Tomcat 专用的内部类
- Shared 类加载器:加载所有 Web 应用共享的类
- WebApp 类加载器:为每个 Web 应用创建独立的类加载器,加载特定 Web 应用的类
- JSP 类加载器:为每个 JSP 页面创建的类加载器
为什么 Tomcat 需要自定义类加载器
1. 隔离性
问题:在一个 Java 应用服务器中可能同时运行多个 Web 应用,这些应用可能使用不同版本的相同库。
解决方案:Tomcat 为每个 Web 应用创建单独的 WebApp 类加载器,实现了类加载的隔离。这使得不同的 Web 应用可以使用同一个类的不同版本,而不会相互干扰。
2. 热部署/热加载
问题:在不重启整个服务器的情况下更新或重新部署单个 Web 应用。
解决方案:通过替换特定应用的 WebApp 类加载器,Tomcat 可以重新加载单个应用而不影响其他应用。当检测到 Web 应用的更新时,Tomcat 会丢弃原有的 WebApp 类加载器及其加载的所有类,创建一个新的类加载器重新加载应用,实现热部署。
3. 资源回收
问题:标准的 Java 类加载器可能导致内存泄漏,因为加载的类在类加载器存在的情况下无法被垃圾回收。
解决方案:当停止或重新部署 Web 应用时,Tomcat 可以丢弃相应的 WebApp 类加载器,使其加载的类和对象可以被垃圾回收,从而有效释放资源。
4. Web 应用间的依赖管理
问题:一些库应该在 Web 应用间共享,而另一些应该保持隔离。
解决方案:Tomcat 使用 Common 和 Shared 类加载器来加载共享的库,同时使用 WebApp 类加载器来加载应用特定的库,实现了灵活的依赖管理。
5. 类加载顺序的控制
问题:Java 默认遵循双亲委派模型,但这种模型在某些 Web 服务场景下不够灵活。
解决方案:Tomcat 的类加载器可以在特定情况下打破双亲委派模型,允许 Web 应用优先加载自己的类,而不是委托给父类加载器。这对于处理 Servlet API 等情况非常重要,Web 应用可能需要使用特定版本的 API,而不是服务器提供的版本。
类加载器委派模型的修改
Tomcat 修改了标准的双亲委派模型:
- 对于 Java SE 核心类(如 java.lang.*),仍然遵循双亲委派模型,由 Bootstrap 类加载器加载
- 对于 Web 应用自身的类,WebApp 类加载器会首先尝试加载,而不是立即委托给父类加载器
- 只有当 WebApp 类加载器找不到请求的类时,才会委托给父类加载器
这种修改使得 Tomcat 可以实现更灵活的类加载策略,满足 Web 应用服务器的特殊需求。
实际应用示例
假设有两个 Web 应用 A 和 B,它们使用不同版本的同一个库:
- 应用 A 使用 log4j 1.2.17
- 应用 B 使用 log4j 2.14.1
在标准 Java 类加载机制下,这两个版本会冲突。但在 Tomcat 中:
- 为应用 A 创建的 WebApp 类加载器加载 log4j 1.2.17
- 为应用 B 创建的 WebApp 类加载器加载 log4j 2.14.1
这样两个应用可以同时运行而不会相互干扰。
性能考虑
Tomcat 的类加载器架构虽然增加了复杂性,但实际上可以提高性能:
- 加载时间优化:只有在需要时才加载类,减少启动时间
- 内存优化:不同 Web 应用可以共享常用库的单一实例
- 资源释放:停止应用时可以释放其占用的类加载资源
结论
Tomcat 使用自定义类加载器是一种精心设计的解决方案,用于应对 Java Web 服务器环境中的特殊挑战。这种设计模式使 Tomcat 能够提供隔离性、热部署、资源管理和灵活性,同时仍然保持与 Java 类加载机制的兼容性。通过理解这种机制,开发人员可以更好地处理类加载相关的问题,并有效利用 Tomcat 提供的功能。