WildFly 26集群内存膨胀与部署挂起问题求助(JSF应用)
Java EE 8 JSF应用WildFly集群内存与部署问题排查优化
应用基础信息
- 技术栈:Java EE 8 + JSF(遗留系统)
- 核心特性:包含大型动态表单
- 部署包:WAR包约120MB,内置DeltaSpike及其他大型依赖库
- 多租户模式:同一WAR包重复部署3-4次
- 并发规模:最大并发用户约300人
- Bean作用域:仅1个SessionScoped Bean,其余以ViewScoped为主,少量RequestScoped
WildFly与JVM配置
- WildFly版本:26,使用
full-ha配置文件 - 堆内存:48GB
- 监控方式:通过
javaagent集成Prometheus + Jolokia - JVM启动参数:
-XX:+UseG1GC -XX:ConcGCThreads=12 -XX:ParallelGCThreads=22 -XX:MaxGCPauseMillis=1000 -XX:G1HeapWastePercent=2 -XX:G1ReservePercent=15 -XX:+UnlockExperimentalVMOptions -XX:G1OldCSetRegionThresholdPercent=15 -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1NewSizePercent=20 -XX:G1MaxNewSizePercent=25 -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=2048M -XX:InitiatingHeapOccupancyPercent=20 -Dpolyglot.js.nashorn-compat=true -Dpolyglot.engine.WarnInterpreterOnly=false
现存问题
1. 内存持续膨胀
- JVM堆内存缓慢攀升至48GB后维持高位,无法有效回收
- G1GC垃圾回收效果差,老年代占用居高不下
- 疑似根因:内存泄漏、过度缓存、长期存活的会话/ViewScoped Bean
2. 部署导致集群异常
- 通过管理控制台执行“替换部署”操作时,节点出现挂起
- 应用关闭耗时2-3分钟,最终需手动重启节点
- 节点重启后所有用户强制登出:非序列化作用域Bean无法参与粘性会话恢复
限制条件
系统需现代化改造,但当前受时间、预算限制,无法进行重写。
需求与优化建议
GC调优建议
- 调整G1GC触发阈值:当前
XX:InitiatingHeapOccupancyPercent=20设置过低,48GB堆下会导致GC过于频繁,建议调至40-50,减少并发GC触发次数,避免CPU资源过度消耗。 - 优化新生代比例:当前
G1MaxNewSizePercent=25仅分配12GB给新生代,300并发用户下可能导致对象过早进入老年代。建议调至35-40,让更多短期对象在新生代回收,降低老年代压力。 - 调整混合回收阈值:
G1MixedGCLiveThresholdPercent=90过高,意味着只有老年代区域存活对象占比90%才会被纳入混合回收,容易导致老年代堆积。建议降至70-75,让G1更早处理老年代可回收区域。 - 禁用实验性参数:
-XX:+UnlockExperimentalVMOptions搭配的G1OldCSetRegionThresholdPercent在WildFly 26对应JDK版本(推测JDK11+)中可能不稳定,建议移除该参数,使用默认值。 - 添加GC日志:增加
-Xlog:gc*,gc+heap=trace:file=gc.log:time,uptime:filecount=5,filesize=100M参数,收集GC日志用于分析内存增长趋势和回收效率。
避免部署时集群挂起的方案
- 采用滚动部署策略:不直接在集群所有节点同时执行替换部署,而是逐个节点操作:先将节点从负载均衡中移除,停止应用、部署新版本、启动后重新加入集群,再处理下一个节点。既避免集群整体挂起,也减少用户登出影响。
- 优化应用关闭逻辑:检查ViewScoped/SessionScoped Bean的
@PreDestroy方法,避免销毁时执行耗时操作(如大量数据库查询、文件IO),缩短应用关闭时间。 - 启用WildFly优雅停机:编辑
standalone-ha.xml或domain.xml,在subsystem=undertow下配置:
给用户30秒完成当前请求,再关闭应用。<server name="default-server"> <http-listener name="default" socket-binding="http" graceful-shutdown-timeout="30"/> </server> - 修复Bean序列化问题:将所有SessionScoped/ViewScoped Bean实现
Serializable接口,确保粘性会话在节点重启后可恢复,避免用户强制登出。
减少内存占用与检测内存泄漏的方法
- JSF视图作用域优化:
- 启用视图状态序列化:在
faces-config.xml中添加<state-saving-method>server</state-saving-method>和<serialize-state>true</serialize-state>,将ViewScoped Bean序列化存储,减少堆内存占用。 - 缩短视图过期时间:在
web.xml中设置<context-param><param-name>javax.faces.DEFAULT_MAX_INACTIVE_INTERVAL</param-name><param-value>1800</param-value></context-param>(30分钟),让闲置ViewScoped Bean更快被回收。
- 启用视图状态序列化:在
- 内存泄漏检测:
- 使用JDK自带工具:定期执行
jmap -dump:live,format=b,file=heapdump.hprof <PID>生成堆转储文件,再用jhat heapdump.hprof分析大对象和引用链,定位未被回收的Bean或缓存对象。 - 利用Prometheus监控:关注
jvm_memory_used_bytes(分老年代、新生代、元空间)、jvm_gc_collection_seconds_sum等指标,判断内存增长是缓慢泄漏还是正常缓存累积。
- 使用JDK自带工具:定期执行
- 多租户部署优化:同一WAR包多次部署会重复加载类,导致元空间占用过高。建议将公共依赖(如DeltaSpike、JSF API)提取到WildFly的
modules目录中,让多个部署实例共享类加载,减少元空间和堆内存的重复占用。
WildFly多部署JSF应用运行经验
- 类加载隔离:多租户部署时,在WildFly的
jboss-web.xml中添加<class-loading>org.wildfly.extension.undertow.deployment.UndertowDeploymentClassLoadingSetup</class-loading>,启用独立类加载,避免类冲突。 - 会话复制优化:在
full-ha配置下,调整Infinispan复制模式为ASYNC,减少会话同步对请求性能的影响;同时设置会话复制超时时间,避免无效会话占用集群资源。 - 监控多实例资源:通过Jolokia监控每个部署实例的内存、CPU使用情况,定位资源占用过高的租户实例,针对性优化该租户业务逻辑。
内容的提问来源于stack exchange,提问作者G33RY
相关产品推荐
相关产品推荐

