tomcat 通过为每个应用分配独立的 webappclassloader 实现静态变量隔离,同一类名由不同加载器加载即为不同 class,各自拥有独立静态空间;本地优先加载策略确保应用内类不被父加载器共享,而容器 api 等共享类须由上层加载器统一供给。

Tomcat 隔离不同应用的 static 静态变量,靠的不是“禁止共享”,而是让每个应用加载出**本质上不同的类**——同一个类名,在不同 WebAppClassLoader 下,就是两个完全独立的 Class 对象,各自拥有自己的静态变量空间。
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
每个应用配一个专属类加载器
Tomcat 启动时,为每个部署的 WAR 包(如 app1、app2)创建独立的 WebAppClassLoader 实例。JVM 判定类是否相同,必须同时满足:
• 全限定类名相同
• 加载它的类加载器实例也相同
所以:
• app1/WEB-INF/classes/com/example/Config.class → 由 WebAppClassLoader@A 加载 → 生成 Class A
• app2/WEB-INF/classes/com/example/Config.class → 由 WebAppClassLoader@B 加载 → 生成 Class B
Class A 和 Class B 在 JVM 中互不可见,它们各自的 public static int VERSION = 1 是两块独立内存,修改一个不影响另一个。
本地优先加载,确保同名类不被父加载器“抢走”
WebAppClassLoader 重写了 loadClass() 方法,执行顺序是:
• 先查自己已加载的缓存(findLoadedClass)
• 再扫描本应用的 /WEB-INF/classes 目录
• 再遍历 /WEB-INF/lib 下所有 JAR
• 仅当以上全部失败,才调用 super.loadClass 委托给父加载器(如 CommonClassLoader)
这个“先本地、后委派”的策略,保证了 app1 和 app2 即使都放了 slf4j-api-1.7.30.jar 和 slf4j-api-2.0.9.jar,也会各自加载自己的版本,不会因父加载器提前加载而变成全局一份。
共享类走父加载器,避免静态变量意外复用
像 javax.servlet.Servlet、org.apache.catalina.Context 这类通用类型,由 CommonClassLoader 统一加载(位于 $CATALINA_HOME/lib/),所有应用共用同一份 Class 和静态变量。这种设计既节省资源,又保证容器契约一致。
但关键点在于:这些类**不会出现在应用自己的 WEB-INF 目录里**。一旦你把 servlet-api.jar 放进某个 WAR 的 /WEB-INF/lib,WebAppClassLoader 就会优先加载它——这时就可能产生两个 Servlet 接口类,导致 ClassCastException。所以共享类必须严格由上层加载器供给,应用只负责使用,不负责提供。
验证隔离是否生效的实用方法
别只看代码,要观察运行时行为:
• 在目标类的 static 块中打印加载器:System.err.println(YourClass.class.getClassLoader())
• 启动时加 -verbose:class,比对日志中 [Loaded com.example.Config from …] 的路径和上面打印的加载器是否匹配
• 跨应用赋值测试:让 app1 执行 Config.setPort(8081),再让 app2 调用 Config.getPort() —— 若返回初始值(比如 0),说明隔离成功;若返回 8081,说明两个应用实际用了同一个 Class 加载器或 TCCL 没切换对
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










