Quarkus 服务容器内存异常增长问题复盘
一、问题概述
在生产环境中,一个基于 Quarkus + JDK 21 的服务出现容器内存持续增长的问题。
服务特征如下:
- 运行在 Docker 容器中
- JVM Heap 限制为 128MB
- 初始内存占用约 300MB
- 运行 1~2 天后,容器 RSS 增长至 600MB~1.2GB
- 无 OOM、无明显 GC 异常
- 服务功能和 QPS 未发生显著变化
该问题存在潜在风险:
- 容器节点内存被持续侵占
- 长时间运行后可能触发宿主机 OOM
- 难以通过 JVM 常规监控发现
二、问题影响
- 容器 RSS 与 JVM Heap 严重不匹配,误导监控判断
- 内存增长不可预测,难以设置合理的资源水位
- 存在节点级连锁风险,影响同机其他服务
三、排查过程
1. Docker 层面确认问题
通过 docker stats 观察到:
- 服务启动后 RSS ≈ 300MB
- 运行时间越长 RSS 越高
- 重启后内存立即恢复到初始水平
初步判断为“运行期内存占用增长”。
2. JVM Heap 排查
使用以下命令查看 Heap 状态:
jcmd 1 GC.heap_info结果显示:
- Heap committed 始终为 128MB
- Old 区使用率稳定
- 无 Full GC 异常
结论:不是 Java 对象泄漏。
3. 线程与栈内存排查
jcmd 1 Thread.print
ls /proc/1/task | wc -l- Java 线程数量稳定在 50~60
- 单线程栈大小已限制为 512KB
结论:线程与栈内存不是主要增长来源。
4. Direct Memory 排查
jcmd 1 GC.class_histogram | grep DirectByteBuffer- DirectByteBuffer 数量与容量均很小
- 未发现直接内存异常累积
结论:不是 NIO 直接内存泄漏。
5. Native Memory Tracking 关键发现
启用 JVM 参数:
-XX:NativeMemoryTracking=summary执行:
jcmd 1 VM.native_memory summary发现:
- JVM 统计的 committed 内存 ≈ 300MB~350MB
- docker stats 显示 RSS ≈ 600MB~1.2GB
- 两者存在数百 MB 的差距
这表明:
有大量内存不在 JVM 管理与统计范围内。
四、根因分析
glibc malloc arena 机制导致 RSS 膨胀
最终定位问题为 glibc malloc arena 行为。
在 Linux 系统中:
- glibc 为多线程 malloc 提供 arena
- 默认 arena 数量与 CPU 核数相关
- 多线程 I/O 框架(如 Netty、Vert.x)会频繁触发 arena 分配
- arena 中的内存即使空闲,也不会主动归还给操作系统
在容器环境下表现为:
- JVM Heap 稳定
- Native Memory 看似正常
- 进程 RSS 持续增长且无法回收
五、解决方案
1. 限制 glibc arena 数量
在容器环境变量中添加:
MALLOC_ARENA_MAX=2效果:
- 强制限制 arena 数量
- 显著减少内存碎片
- RSS 不再随运行时间增长
2. 保留 NMT 用于长期监控
-XX:NativeMemoryTracking=summary用于:
- 定位 JVM 内存结构变化
- 区分 JVM 内存与系统内存问题
六、验证结果
修复前
- 启动 RSS ≈ 300MB
- 运行 24 小时 RSS ≈ 600MB
- 运行 48 小时 RSS ≈ 700MB+
修复后
- 启动 RSS ≈ 250MB~300MB
- 运行 48 小时 RSS ≈ 300MB~350MB
- 内存占用稳定
Java Heap、Metaspace、线程数均无明显变化。
七、复现与反向验证
在一次实验中,因环境变量拼写错误:
MALLOC_ARENA_MAX1=2导致 arena 实际未被限制。
结果为:
- 服务运行 1 天后 RSS 再次增长至 ≈ 600MB
该实验从反向再次验证了问题根因。
八、经验总结
- 容器中 Java 进程的 RSS 不等于 JVM 内存
- glibc arena 是容器内存异常增长的常见隐蔽来源
- JVM 参数无法完全解决系统级内存问题
- Java 服务在容器化部署时必须同时关注 JVM 与 libc 行为
九、后续行动项
- 所有 Java 容器统一添加
MALLOC_ARENA_MAX=2 - 关键服务保留 NMT 用于排查
- 更新团队 Java 容器内存排查手册
- 在容量评估中区分 Heap 与 RSS
如果你愿意,我可以下一步直接帮你整理成:
- 《Java 容器内存排查 Checklist》
- 《为什么 Heap 很小但容器内存很大》的 FAQ
- 或一页 “Leader 可读版”总结说明
你只要说一声。
作者:张三 创建时间:2026-01-06 09:45
最后编辑:张三 更新时间:2026-01-14 17:39
最后编辑:张三 更新时间:2026-01-14 17:39