Tomcat反复热部署Spring Boot war包后出现Metaspace OOM问题
Spring Boot WAR包反复部署卸载导致Tomcat Metaspace内存溢出问题
问题概述
基于Spring Boot 2.7.5开发的简单应用,通过JPA访问Oracle 19c单表,打包为WAR包后在Tomcat 9.0.75上反复执行部署与卸载操作,多次后触发Metaspace OOM(元空间内存溢出),VisualVM监控显示平台期时Tomcat已出现挂起。
环境信息
- OS:Ubuntu 22.04.3
- JVM:Oracle Java 11.0.18 / Oracle Java 17.0.17(均验证过)
- Tomcat:9.0.75
- Spring Boot:2.7.5
- Oracle:19c
- Oracle JDBC:
com.oracle.database.jdbc:ojdbc11:23.2.0.0
核心代码
Policy.java(实体类)
@Getter @Setter @Entity @Table(name = "policy_m") public class Policy { @Column(name = "policy_agreement_id") private Long policyAgreementId; @Id @Column(name = "policy_num") private String policyNum; }
PolicyRepository.java(数据访问层)
@Repository public interface PolicyRepository extends JpaRepository<Policy, String> { @Query(value = "select * from policy_m where policy_num = :policyNum", nativeQuery = true) Policy getPolicyByPolicyNum(@Param("policyNum") String policyNum); }
PolicyServiceImpl.java(业务逻辑层)
@Service public class PolicyServiceImpl implements PolicyService { @Autowired private PolicyRepository policyRepository; public Policy getPolicyByPolicyNum(String policyNum) { return policyRepository.getPolicyByPolicyNum(policyNum); } }
DemoController.java(控制层)
@RestController public class DemoController { @Autowired private PolicyService policyService; @GetMapping("/{policyNum}") public Policy database(@PathVariable("policyNum") String policyNum) { return policyService.getPolicyByPolicyNum(policyNum); } }
复现步骤
- 打包生成应用WAR包
- 通过Tomcat管理控制台部署该WAR包
- 通过Tomcat管理控制台卸载该应用
- 重复执行步骤2-3,直至抛出Metaspace OOM异常
问题分析与解决方案
可能原因
Metaspace溢出本质是类加载器无法被GC回收,导致类元数据持续累积。结合场景,核心诱因包括:
- JDBC驱动类加载器泄漏:ojdbc11可能存在静态引用或未释放资源,导致Web应用类加载器无法被回收。
- Spring/JPA资源未彻底释放:Spring容器关闭时,JPA代理类、EntityManagerFactory等资源未完全清理,绑定到Web类加载器上。
- Tomcat类加载器机制问题:反复部署卸载时,Web应用类加载器未被正确回收,类元数据持续堆积在Metaspace。
解决方案
调整Tomcat类加载器配置
在conf/context.xml中添加参数避免JAR锁定,确保类加载器可被回收:<Context antiResourceLocking="true" antiJARLocking="true"> <!-- 原有配置 --> </Context>优化JDBC驱动加载方式
将ojdbc11依赖从WAR包中移除,放到Tomcat的lib目录下,由Tomcat共享类加载器加载,避免每个Web应用重复加载驱动类,减少Metaspace占用。调整Metaspace内存参数
在Tomcat的bin/catalina.sh(Linux)或catalina.bat(Windows)中添加JVM参数,临时缓解内存压力:JAVA_OPTS="$JAVA_OPTS -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"定位类加载器泄漏点
使用jmap生成堆转储文件分析未回收的类加载器:jmap -dump:format=b,file=heapdump.hprof <Tomcat进程ID>检查是否存在第三方库静态缓存持有类加载器引用的情况,针对性清理资源。
内容的提问来源于stack exchange,提问作者Stephen Wong
相关产品推荐
相关产品推荐

