JBoss EAP 7.0.0反复部署Spring WAR出现OutOfMemoryError求助
核心现象总结
你遇到的问题是:在JBoss EAP 7.0.0中反复部署/移除Spring WAR包3-4次后,出现两种OutOfMemoryError:
- Java堆空间不足(关联Netty内存分配调用栈)
- GC开销限制超出(关联Hibernate自定义线程调用栈)
通过VisualVM观察到关键异常表现:
- 每次部署后加载的类数量只增不减,移除应用后未释放
- 线程数持续增长,无法回退到初始状态
而相同应用在Tomcat 8.5.24中无此问题。
可能的根因分析
这本质是类加载器泄漏+线程/资源未正确释放的组合问题,核心差异来自JBoss EAP与Tomcat的类加载机制:
JBoss模块化类加载器的特殊性
JBoss EAP 7基于WildFly,采用模块化类加载体系(不同于Tomcat的扁平类加载)。如果Spring应用的依赖(比如Hibernate、Netty)和JBoss自带的系统模块版本冲突,或者应用的类加载器在卸载时被残留对象(线程、单例Bean等)持有引用,就会导致整个类加载器无法被GC回收,进而泄漏大量类对象和堆内存。Netty资源未关闭
报错栈中出现Netty调用链,说明要么是应用自身使用的Netty组件(比如EventLoopGroup、Channel)在应用停止时未调用shutdownGracefully()释放资源,要么是JBoss内部Netty与应用打包的Netty版本冲突,导致资源无法被正确清理,残留的线程和内存缓冲区持续占用堆空间。Hibernate与自定义线程泄漏
第二个报错里的MyThread_ViewsDtoGeneration是自定义线程,如果这个线程没有在应用卸载时被中断、清理,它会持有Spring上下文或Hibernate SessionFactory的引用,进而绑定类加载器,阻止GC回收。另外,Hibernate的代理类、SessionFactory如果没有随Spring上下文一起销毁,也会导致类对象泄漏。Spring上下文未彻底销毁
JBoss部署Servlet应用时,如果Spring的ContextLoaderListener没有被正确触发销毁逻辑,或者Spring Boot应用的SpringBootServletInitializer实现有问题,会导致Spring上下文残留,大量Bean对象和类加载器无法被回收。
针对性解决步骤
1. 确保Spring上下文正确销毁
- 检查web.xml中是否正确配置了
ContextLoaderListener,且无自定义逻辑阻止其contextDestroyed方法执行 - 如果是Spring Boot应用,确保
SpringBootServletInitializer的onStartup方法正确初始化上下文,且应用停止时会触发上下文关闭
2. 排查并修复Netty资源泄漏
- 如果应用自身使用Netty:在Spring的
@PreDestroy方法或Servlet的destroy方法中,显式调用EventLoopGroup.shutdownGracefully()和Channel.close() - 避免应用打包Netty依赖:在
pom.xml中将Netty依赖设置为<scope>provided</scope>,使用JBoss自带的Netty模块;如果必须打包,在jboss-deployment-structure.xml中排除JBoss的Netty模块:
<jboss-deployment-structure> <deployment> <exclude-modules> <module name="io.netty"/> </exclude-modules> </deployment> </jboss-deployment-structure>
3. 清理Hibernate与自定义线程
- 自定义线程(比如
MyThread_ViewsDtoGeneration)要实现优雅关闭逻辑:在应用停止时发送中断信号,线程内部捕获InterruptedException并清理资源 - 确保Hibernate的SessionFactory随Spring上下文销毁:检查
LocalSessionFactoryBean的配置,确认destroy-method="close"已正确设置,或者使用Spring的@Bean(destroyMethod = "close")注解
4. 调整JBoss类加载配置
- 排除JBoss自带的JPA模块,避免和应用的Hibernate冲突:在
jboss-deployment-structure.xml中添加:
<jboss-deployment-structure> <deployment> <exclude-subsystems> <subsystem name="jpa"/> </exclude-subsystems> </deployment> </jboss-deployment-structure>
- 配置Hibernate代理工厂,避免代理类泄漏:在
persistence.xml或Spring配置中添加:
hibernate.proxy.factory_class=org.hibernate.proxy.pojo.javassist.JavassistProxyFactory
5. 临时缓解与排查工具
- 调整JBoss堆内存:修改
standalone.conf(Linux)或standalone.conf.bat(Windows)中的JAVA_OPTS,例如:
JAVA_OPTS="$JAVA_OPTS -Xmx2G -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/jboss"
- 开启JBoss类加载泄漏检测:在
standalone.xml的deployment-scanner子系统中添加enable-leak-detection="true":
<subsystem xmlns="urn:jboss:domain:deployment-scanner:2.0"> <deployment-scanner path="deployments" relative-to="jboss.server.base.dir" scan-interval="5000" enable-leak-detection="true"/> </subsystem>
6. 升级JBoss EAP版本
JBoss EAP 7.0.0是早期版本,官方后续版本(比如7.4.x)修复了大量类加载和资源泄漏的问题,升级到最新稳定版能从根本上解决很多这类兼容性问题。
内容的提问来源于stack exchange,提问作者Marc Gil Sendra

