通过日志优化 Ubuntu 上 Tomcat 的 JVM 参数
一 准备可观测性日志
- 启用并落盘 GC 日志(建议统一放在 $CATALINA_BASE/logs/)
- 推荐组合(JDK 8 常用):
- -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -Xloggc:$CATALINA_BASE/logs/gc.log
- 如需观察停顿分布,可加:-XX:+PrintGCApplicationStoppedTime
- 在 bin/catalina.sh 的合适位置(如注释行下方)追加到 JAVA_OPTS,例如:
- JAVA_OPTS=“$JAVA_OPTS -server -Xms2g -Xmx2g -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -Xloggc:$CATALINA_BASE/logs/gc.log -XX:+PrintGCApplicationStoppedTime”
- 打开 Tomcat 访问日志便于关联慢请求与 GC 波动
- 在 conf/server.xml 的 内配置 AccessLogValve,输出如:%h %l %u %t “%r” %s %b %D(其中 %D 为请求耗时 ms),并按日滚动切割,便于定位 GC 前后请求延迟变化。
- 可选:开启 JMX 便于外部监控(VisualVM/JConsole),在 JAVA_OPTS 增加:
- -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false(生产请启用认证与加密)。
二 从日志中识别问题
- GC 日志关键信号
- 频繁 Full GC:常见于老年代空间不足或元空间(JDK 8 为 PermGen)不足;日志中会出现 “Full GC” 且 tenured 区占用接近上限。
- 启动阶段短时间多次 Full GC:多见于 PermGen/Metaspace 初始过小,类加载触发 Full GC;日志中 “Perm” 或 “Metaspace” 容量打满。
- GC 停顿过长:关注 [Times: user=…, sys=…, real=…] 的 real 时间,若接近或超过业务可容忍阈值,需优化收集器或堆布局。
- 辅助实时监控
- 观察整体 GC 负载:jstat -gcutil 1000(每秒输出一次各代使用率与 GC 次数/时间)。
- 查看堆配置与运行时分区:jmap -heap 。
- 抓取线程栈定位 CPU 热点或阻塞:jstack > stack.log,结合 top -H 找高占用线程的 nid 再转 16 进制检索。
- 可视化分析
- 将 gc.log 上传 GCEasy 等工具,快速得到吞吐量、最大/平均停顿、Young/Full GC 次数与原因分布,用于指导下一轮参数调整。
三 基于日志的迭代调参步骤
- 堆大小与稳定性
- 若 Full GC 频繁或老年代长期打满:适度增大 -Xms/-Xmx(建议等值,避免运行期扩容抖动),并观察 Full GC 是否显著减少。
- 若 Young GC 过密且对象生命周期短:适度增大 新生代(如 -Xmn 或 -XX:NewRatio),让短命对象多在年轻代回收。
- 元空间(JDK 8 为 PermGen)
- 启动或运行中频繁因元空间触发 Full GC:提高 -XX:MetaspaceSize(初始阈值)与 -XX:MaxMetaspaceSize(上限),减少因扩容触发的 GC。
- 收集器选择与目标
- 低延迟优先(交互/网关/API):优先 G1 GC,设置目标停顿 -XX:MaxGCPauseMillis=100~200,并配 -XX:+UseG1GC;必要时调节并发线程与 IHOP(如 -XX:InitiatingHeapOccupancyPercent)。
- 吞吐优先(后台批处理):可选 Parallel GC(吞吐量优先,停顿不可控性更高)。
- 传统 CMS(JDK 8 仍可用):组合 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC,并通过 -XX:CMSInitiatingOccupancyFraction 提前触发并发回收,减少并发失败退化 Full GC 的风险。
- 线程与栈
- 若线程数接近系统/容器上限或栈深较大:适度调整 -Xss,在可用内存与可创建线程数之间平衡。
- 变更验证
- 每次只变更一类参数,保留上一版 gc.log 以便对比;用 jstat/jmap 与业务指标(RT、TP99、错误率)联合评估,确认“停顿下降、吞吐提升、Full GC 减少”。
四 示例配置与落地检查
- 示例 A(JDK 8,CMS,中等堆)
- JAVA_OPTS=“$JAVA_OPTS -server -Xms2g -Xmx2g -Xmn768m
-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:CMSInitiatingOccupancyFraction=70
-XX:+UseCMSCompactAtFullCollection -XX:+CMSParallelRemarkEnabled
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -Xloggc:$CATALINA_BASE/logs/gc.log
-XX:+PrintGCApplicationStoppedTime”
- 示例 B(JDK 8/11+,G1,低延迟目标)
- JAVA_OPTS=“$JAVA_OPTS -server -Xms4g -Xmx4g
-XX:+UseG1GC -XX:MaxGCPauseMillis=150
-XX:InitiatingHeapOccupancyPercent=45
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -Xloggc:$CATALINA_BASE/logs/gc.log
-XX:+PrintGCApplicationStoppedTime”
- 落地检查
- 重启后在 $CATALINA_BASE/logs/catalina.out 与 gc.log 确认参数已生效(如 “Command line” 能看到完整 JAVA_OPTS)。
- 用 jps 获取 ,执行 jstat -gcutil 1000 30、jmap -heap ,核对堆与 GC 行为是否符合预期。
五 常见陷阱与修正
- 只设 -Xmx 不设 -Xms:运行期频繁扩容会放大 GC 抖动,建议等值。
- 过早/过晚触发并发回收(CMS):并发失败会退化 Full GC,需结合日志与负载调 CMSInitiatingOccupancyFraction。
- 将 -Xmn 设得过大或过小:过大挤占老年代易 Full GC,过小 Young GC 过密;结合对象生命周期与 GC 日志迭代。
- 忽视元空间:类加载多时未调 MetaspaceSize/MaxMetaspaceSize,会在启动/运行中频繁 Full GC。
- 日志未落盘或未滚动:无法回溯分析;务必启用 -Xloggc 并做按日切割。