利用日志优化 Ubuntu 上 Tomcat 内存的实操指南
一、建立可观测性基础
- 启用并规范 GC 日志:在 $CATALINA_HOME/bin/setenv.sh 中设置环境变量(若文件不存在请创建),将 GC 日志写入 Tomcat 日志目录,便于统一采集与轮转。示例(JDK 8/11 通用):
- 建议参数:
- -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/opt/tomcat/logs/gc.log
- 可选:添加 -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=10M 做日志切分
- 示例 setenv.sh:
- export CATALINA_OPTS=“-Xms1024m -Xmx2048m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/opt/tomcat/logs/gc.log”
- 重启后在 /opt/tomcat/logs/ 下应能看到 gc.log。
- 配置应用与容器日志轮转:编辑 conf/logging.properties 控制日志级别,避免产生过大日志;对 catalina.out 使用 logrotate 按日轮转并保留历史,防止磁盘被日志占满间接引发内存/稳定性问题。示例 logrotate 片段:
- /opt/tomcat/logs/catalina.out {
- daily
- rotate 7
- missingok
- notifempty
- copytruncate
- }
- 建议同时落盘并记录关键指标(如 Heap/Meta/Direct 使用量、线程数、类加载数),便于与 GC 日志联动分析。
二、从日志中识别内存压力与泄漏信号
- GC 日志判读要点(关注趋势而非单条记录):
- 频繁 Full GC 且回收后老年代占用几乎不变,常见于内存泄漏或堆过小。
- GC 暂停时间明显变长,提示回收压力大,可能需要增大堆或优化对象生命周期。
- 年轻代回收频繁且晋升过快,考虑增大年轻代或优化短生命周期对象创建。
- 启用与获取堆转储用于根因定位:
- 在 setenv.sh 临时加入 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/tomcat/logs/,当发生 OutOfMemoryError 时自动落盘 .hprof。
- 也可通过 jmap -dump:format=b,file=/opt/tomcat/logs/heap.hprof 在线抓取。
- 结合分析工具深挖泄漏点:
- 使用 Eclipse MAT 或 VisualVM 打开 .hprof,查看 Dominator Tree、Histogram、可疑引用链,定位未关闭的资源、缓存膨胀、监听器/线程局部变量泄漏等常见根因。
三、基于日志洞察的参数调优
- 堆与元空间(由 GC/Metaspace 日志与 OOM 触发情况驱动):
- 若 GC 日志显示老年代长期吃紧或频繁 Full GC,适度上调 -Xmx/-Xms(建议两者等值,减少运行时扩缩容抖动);若 Metaspace 持续增长且不回落,上调 -XX:MaxMetaspaceSize 并排查类加载泄漏(如热部署残留、第三方库重复加载)。
- 示例(4GB 内存的通用起步值):-Xms1024m -Xmx2048m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m(请结合业务压测校准)。
- 线程与连接器(由访问日志/线程池饱和迹象驱动):
- 若访问日志显示高并发下响应变慢且线程池打满,适度提升 maxThreads,并配合合理的 acceptCount 与连接超时,避免请求堆积与资源争用。
- 示例(HTTP/1.1 NIO):
- <Connector port=“8080” protocol=“HTTP/1.1”
- connectionTimeout=“20000”
- maxThreads=“200”
- minSpareThreads=“10”
- acceptCount=“100”
- redirectPort=“8443”
- enableLookups=“false”
- URIEncoding=“UTF-8” />
- 注意:线程过多会增加上下文切换与内存占用(每个线程有线程栈,默认约 1MB,可通过 -Xss 调整,谨慎增大)。
- GC 策略选择(由 GC 日志停顿与吞吐目标驱动):
- JDK 8 常用 -XX:+UseG1GC;JDK 11+ 默认 G1 已较成熟。目标是降低 Full GC 次数与停顿时间,配合日志持续验证效果。
四、落地流程与验证
- 基线采集:部署上述日志与参数,稳定运行一个业务高峰周期,收集 gc.log、catalina.out、访问日志与必要的 heap.hprof。
- 指标与现象对照:
- GC 频率/停顿、老年代占用曲线、Full GC 后回收效果
- 线程峰值、请求排队/超时、应用错误率
- Metaspace 是否“只增不减”
- 迭代调优:每次只调整一个变量(如 -Xmx、maxThreads、GC 策略),用下一周期日志验证;必要时抓取新的 heap dump 复核对象分布。
- 运行期观测与复核:
- 使用 jstat -gc 观察各代容量与回收次数;需要时再次 jmap 导出堆快照做对比分析。
五、常见陷阱与排查清单
- 只加内存不治本:若日志显示 Full GC 后占用不降或 Metaspace 持续增长,优先排查代码/配置(未关闭资源、缓存无上限、类加载器泄漏、热部署残留)。
- 日志本身导致问题:catalina.out 无限增长会占满磁盘并引发稳定性问题,务必配置 logrotate 按日轮转并压缩归档。
- 线程与连接泄漏:访问日志与线程池指标异常、连接超时增多,需核对连接池配置与业务是否正确释放连接/会话。
- 元空间膨胀:升级或替换依赖后类数激增,检查是否有重复加载、类加载器未释放等问题,并合理设置 -XX:MaxMetaspaceSize。
- 32 位环境限制:避免在大内存需求场景使用 32 位 JVM/系统,单进程可用内存受限,易出现无法分配大堆等问题。