Tomcat 不只是一个“跑 war 包的服务器”。它同时承担 Web 容器、类加载隔离和外部资源管理的职责。理解 Tomcat 的类加载和 JNDI,有助于排查 jar 冲突、类找不到、数据库连接池配置和多应用隔离问题。
JVM 类加载默认是父类委托
普通 JVM 类加载遵循父类委托模型:当前类加载器先把加载请求交给父加载器,父加载器再往上交,直到顶层。顶层加载不到,才逐层往下尝试。
常见类加载器包括:
- Bootstrap ClassLoader:加载 JDK 核心类。
- Extension / Platform ClassLoader:加载扩展或平台类。
- App ClassLoader:加载应用 classpath 中的类。
- 自定义 ClassLoader:框架或容器按需扩展。
父类委托的好处是保证核心类稳定,不容易被应用随便覆盖。
Tomcat 会为容器和应用划分加载边界
Tomcat 启动时会创建多级类加载器。容器自身依赖、公共库、每个 Web 应用的类和 jar,并不完全处在同一层。
典型边界包括:
- Tomcat 启动类和容器类。
CATALINA_HOME/lib下的公共库。- 每个 Web 应用自己的
WEB-INF/classes和WEB-INF/lib。
这就是为什么同一个 Tomcat 上不同 Web 应用可以使用不同版本的业务 jar,也解释了为什么有些类在容器层能看到、应用层却不一定能正确使用。
Web 应用类加载常用于解决冲突
当应用自己的 class 和 jar 中存在同名类时,加载顺序会影响最终使用哪一个版本。很多“本地调试正常、部署后异常”的问题,都和 classpath、war 包内容和容器公共 lib 有关。
排查时可以按这个顺序看:
- 目标类是否真的打进了
WEB-INF/classes或WEB-INF/lib。 - 容器公共 lib 是否存在另一个版本。
- Maven 依赖是否有冲突。
- 编译产物是否和部署产物一致。
- 是否出现同名包、同名类或老版本 jar 残留。
mvn dependency:tree、IDE 依赖分析、Tomcat 启动日志都很有帮助。
JNDI 适合引用容器管理的外部资源
JNDI 可以让应用通过名称查找外部资源,例如数据源、消息连接工厂或环境变量。资源由容器配置和管理,应用只负责查找和使用。
它的价值在于把资源配置从应用包里拿出来。数据库地址、连接池参数、账号等可以放在容器侧,不必写死在业务代码或 war 包里。
现代 Spring Boot 项目更常用配置文件和连接池 Bean,但在传统 Tomcat 多应用部署、公司统一数据源管理场景下,JNDI 仍然会遇到。
容器边界也是部署边界
Tomcat 的类加载和 JNDI 都在强调同一件事:应用不是孤立运行的,它生活在容器里。
容器会决定类从哪里加载、资源由谁创建、生命周期如何结束。热部署、连接池泄漏、线程未关闭、驱动未注销,都可能让旧应用的类加载器无法释放,最终造成内存问题。
实用结论
Tomcat 类加载排查要看“类在哪一层”,JNDI 排查要看“资源由谁管理”。
理解容器边界后,很多问题会变得清晰:jar 冲突不是玄学,类找不到不是偶然,连接池配置也不只是代码里的一个 Bean。部署环境本身就是应用架构的一部分。




