以下是对这篇原文的总结,部分内容不够详细,请参考原文地址:https://juejin.cn/post/7255634554987020343
内存问题排查方法论
1. 问题定位流程
- 确定进程:使用
ps aux --sort=-%mem
找出内存占用最高的进程 - 分层排查:按照堆内 → 堆外的顺序逐步分析
- 量化分析:通过计算得出具体的内存占用分布
2. 堆内存分析技巧
# 查看堆内存详情
jcmd <pid> GC.heap_info# 查看真实物理内存占用
pmap -X <pid>
3. 堆外内存分析技巧
# 使用Arthas快速查看各内存区域
memory# 计算公式
堆外物理内存 = 总占用物理内存 - heap占用的物理内存
jcmd GC.heap_info
heap:启动参数里指定了Xmx是4G,从命令结果也可以看出约等于 new generation 1.8G + old geration 2G。
Metaspace: committed 约0.1G左右
通过pmap命令可以看到最真实的JVM heap的物理内存的占有量,因为Heap本质是一个内存池,池子的总大小4G,但是实际物理内存一般达不到4G,而Heap的used也只是池子中使用部分的内存。所以还是要通过操作系统的pmap来查询:
如上图,JVM heap是4G的虚拟内存,启动物理内存约占用3144764K即约3G,50M左右置换到了swap空间。
所以heap实际占用约3G。
4. JNI Memory泄漏排查
方向一:gperftools工具分析,查堆栈
# 安装工具
yum install gperftools gperftools-devel graphviz ghostscript# 设置环境变量
export LD_PRELOAD=/usr/lib64/libtcmalloc.so
export HEAPPROFILE=/home/admin/gperftools/heap/hprof# 启动app。。。
# kill app,再运行app_start.sh# 生成对比报告
pprof --pdf --base=heap_before.heap java heap_after.heap > mem-diff.pdf
例如:最大头的是Java_java_util_zip_Inflater_inflateBytes函数在申请堆外内存,共680M,占比680M
方向二:内存块分析,看内存块内容
# 查看内存块分布
(pmap -X <pid> | head -2; pmap -X <pid> | awk 'NR>2' | sort -nr -k6) > pmap.log# dump内存块内容
gdb --batch --pid <pid> --ex 'dump memory 103.dump 0x7f8a78000000 0x7f8a7c000000'# 查看内存数据
strings 103.dump
5. 调用栈定位技巧
# 持续打印线程堆栈
while true; do jstack <pid> | grep -A 20 -B 5 "inflateBytes"; sleep 1; done# 或使用arthas拦截方法调用
stack java.util.zip.Inflater inflate
6. GC验证方法
# 手动触发FullGC并查看对象分布
jmap -histo:live <pid>
7. ptmalloc2内存碎片处理
手动释放内存
# 调用malloc_trim释放碎片内存
gdb --batch --pid <pid> --ex 'call malloc_trim()'
优化参数
export MALLOC_ARENA_MAX=8
export MALLOC_MMAP_THRESHOLD_=131072
export MALLOC_TRIM_THRESHOLD_=131072
export MALLOC_TOP_PAD_=131072
export MALLOC_MMAP_MAX_=65536
8. 解决方案选择
推荐方案:替换内存分配器
# 使用jemalloc或tcmalloc
export LD_PRELOAD=/usr/lib64/libjemalloc.so
# 或
export LD_PRELOAD=/usr/lib64/libtcmalloc.so
业务层面优化
- 修改Kafka压缩算法:gzip → Snappy/LZ4
- 避免使用JVM的gzip相关JNI调用
- 其他
9. 关键排查命令总结
ps aux --sort=-%mem # 找出内存大户进程
jcmd <pid> GC.heap_info # 查看堆内存
pmap -X <pid> # 查看真实内存分布
jstack <pid> # 查看线程堆栈
jmap -histo:live <pid> # 触发GC并查看对象
gdb --batch --pid <pid> --ex 'call malloc_trim()' # 释放碎片内存
10. 注意事项
malloc_trim()
有极小概率导致JVM Crash,使用需谨慎- 使用gperftools时,tcmalloc会间接解决问题,影响问题复现
- 64M内存块是ptmalloc2内存碎片的典型特征
- 不同内存分配器适用场景不同:小内存高并发用TCMalloc,通用场景用ptmalloc2