第一步:5分钟快速定位问题类型
1.1 检查GC日志中的死亡信号
# 查看最后50行,找到死亡时间点
tail -50 gc.log# 或者更精确地查找死亡痕迹
grep -n "OutOfMemoryError\|FULL GC\|Allocation Failure\|concurrent mode failure" gc.log | tail -10
1.2 运行这个快速诊断脚本
#!/bin/bash
echo "=== GC日志快速诊断 ==="
echo "1. 文件信息:"
wc -l gc.log
echo -e "\n2. 关键错误统计:"
echo "OOM错误: $(grep -c "OutOfMemoryError" gc.log)"
echo "Full GC次数: $(grep -c "Full GC" gc.log)"
echo "并发模式失败: $(grep -c "concurrent mode failure" gc.log)"echo -e "\n3. 最后5次关键事件:"
tail -100 gc.log | grep -B2 -A2 "Full GC\|OOM\|failed\|Error" | tail -20echo -e "\n4. 内存增长趋势:"
grep "Full GC" gc.log | tail -5 | awk '{
for(i=1;i<=NF;i++) {
if($i ~ /old=[0-9]+K->[0-9]+K/) {
match($i, /old=([0-9]+)K->([0-9]+)K/, arr)
printf("时间 %s: 老年代 %sK → %sK\n", $1, arr[1], arr[2])
}
}
}'
第二步:分析堆转储文件(核心步骤)
2.1 先查看堆转储基本信息
# 检查文件有效性
jmap -histo heap_dump.hprof 2>&1 | head -5# 如果报错,可能需要指定JRE路径
export JAVA_HOME=/path/to/java
$JAVA_HOME/bin/jmap -histo heap_dump.hprof > /dev/null && echo "堆转储文件正常" || echo "文件可能损坏"
2.2 生成对象直方图(最重要的一步)
# 按对象数量排序
jmap -histo heap_dump.hprof | head -50 > histo_by_count.txt# 按内存占用排序(倒序)
jmap -histo heap_dump.hprof | awk '
BEGIN {print "Class_Name\t<实例数>\t<总大小>\t<平均大小>"}
NR>3 {
gsub(/</, "", $2); gsub(/>/, "", $2)
printf("%s\t%s\t%s\t%.1f\n", $2, $3, $4, $4/$3)
}' | sort -k3 -rn | head -50 > histo_by_size.txtecho "已生成 histo_by_size.txt,查看前50个最大对象"
2.3 重点关注你们自己的代码
# 查找你们公司包名的对象
jmap -histo heap_dump.hprof | grep -E "com\.yourcompany|com\.yourproject" | sort -k4 -rn > our_objects.txt# 如果没有明确包名,查找常见的可疑类
jmap -histo heap_dump.hprof | grep -E "HashMap|ArrayList|LinkedList|ConcurrentHashMap|byte\[\]|char\[\]" > collections.txt
2.4 分析具体问题模式
#!/bin/bash
echo "=== 堆转储详细分析 ==="# 1. 查看byte[]数组占用(通常最大)
echo "1. byte[] 数组分析:"
BYTE_ARRAY_INFO=$(jmap -histo heap_dump.hprof | grep "^\s*[0-9]\+:\s\+[0-9]\+\s\+[0-9]\+\s\+byte\[\]" | head -1)
if [ ! -z "$BYTE_ARRAY_INFO" ]; then
echo " 发现: $BYTE_ARRAY_INFO"
NUM_BYTE_ARRAYS=$(echo $BYTE_ARRAY_INFO | awk '{print $2}')
TOTAL_SIZE=$(echo $BYTE_ARRAY_INFO | awk '{print $3}')
AVG_SIZE=$(echo "scale=2; $TOTAL_SIZE * 1024 / $NUM_BYTE_ARRAYS" | bc)
echo " 平均每个byte[]大小: $AVG_SIZE 字节"
fi# 2. 查看HashMap相关
echo -e "\n2. HashMap分析:"
jmap -histo heap_dump.hprof | grep -E "HashMap\$Node|HashMap\$Entry" | head -3# 3. 查看线程数量
echo -e "\n3. 线程分析:"
THREAD_COUNT=$(jmap -histo heap_dump.hprof | grep " java.lang.Thread" | awk '{print $2}')
if [ ! -z "$THREAD_COUNT" ]; then
echo " 线程数量: $THREAD_COUNT"
if [ $THREAD_COUNT -gt 500 ]; then
echo " ⚠️ 线程数过多!"
fi
fi# 4. 查看Class数量
echo -e "\n4. Class数量:"
jmap -histo heap_dump.hprof | grep " java.lang.Class" | awk '{print " Class数量: "$2}'
第三步:分析对象引用关系
3.1 查找最大的对象并追踪引用链
# 先找到最大的类名
BIGGEST_CLASS=$(head -1 histo_by_size.txt | awk '{print $1}')echo "最大的类是: $BIGGEST_CLASS"
echo "开始分析引用链..."# 如果是你自己的类,直接分析
if [[ $BIGGEST_CLASS == com.* ]]; then
echo "这是你们自己的类,需要找到谁创建了它"
# 获取类的所有实例ID
echo "获取实例ID..."
jmap -histo heap_dump.hprof | grep "$BIGGEST_CLASS" | head -5
fi
3.2 使用jhat命令行模式(需要额外内存)
# 启动jhat分析服务(可能需要4-8G内存)
jhat -J-Xmx4g heap_dump.hprof &
JHAT_PID=$!
echo "jhat启动在端口7000,PID: $JHAT_PID"# 等待启动
sleep 30# 查询最大的对象
curl -s "http://localhost:7000/oql/?query=select%20s%20from%20java.lang.Object%20s%20where%20s.getClass().getName().equals(\"$BIGGEST_CLASS\")" | grep -A20 -B20 "instance"# 完成后杀掉jhat
kill $JHAT_PID
3.3 更轻量的分析方式:使用jstack分析线程
如果有线程转储,可以结合分析:
# 查看哪些线程持有大量内存
grep -A5 -B5 "RUNNABLE" thread_dump.txt | grep -E "com\.yourcompany|正在执行" | head -20
第四步:结合业务代码定位问题
4.1 从堆转储反推代码位置
#!/bin/bash
echo "=== 问题定位指南 ==="# 从histo_by_size.txt中读取可疑类
echo "检查以下可疑类:"
cat histo_by_size.txt | head -10 | while read line; do
CLASS_NAME=$(echo $line | awk '{print $1}')
INSTANCE_COUNT=$(echo $line | awk '{print $2}')
TOTAL_SIZE_MB=$(echo $line | awk '{print $3/1024}')
echo "类: $CLASS_NAME"
echo " 实例数: $INSTANCE_COUNT"
echo " 总内存: ${TOTAL_SIZE_MB}MB"
# 给出可能的问题点
if [[ $CLASS_NAME == *HashMap* ]]; then
echo " 可能问题: 缓存未清理或无限增长的Map"
elif [[ $CLASS_NAME == *ArrayList* ]]; then
echo " 可能问题: 列表无限增长或查询未分页"
elif [[ $CLASS_NAME == *byte[]* ]]; then
echo " 可能问题: 大文件处理或网络传输未使用流"
elif [[ $CLASS_NAME == *Thread* ]]; then
echo " 可能问题: 线程池配置过大或线程泄露"
fi
echo "---"
done
4.2 在代码库中搜索可疑类
# 假设你在项目根目录
PROJECT_ROOT="/path/to/your/project"# 搜索使用HashMap且可能有问题的地方
echo "搜索可能的问题代码..."
find $PROJECT_ROOT -name "*.java" -type f | xargs grep -l "static.*HashMap\|static.*ConcurrentHashMap" | head -10# 搜索缓存相关代码
find $PROJECT_ROOT -name "*.java" -type f | xargs grep -l "Cache\|cache" | head -10# 搜索大对象创建
find $PROJECT_ROOT -name "*.java" -type f | xargs grep -l "new byte\[.*\]\|ByteBuffer.allocate" | head -10
第五步:验证和复现
5.1 创建最小复现脚本
#!/bin/bash
echo "=== 内存问题复现脚本 ==="# 1. 提取关键参数
echo "从GC日志提取关键参数:"
LAST_FULL_GC=$(grep "Full GC" gc.log | tail -1)
echo "最后一次Full GC: $LAST_FULL_GC"# 2. 计算内存增长率
echo -e "\n计算内存增长率:"
grep "Full GC" gc.log | tail -10 | awk '{
for(i=1;i<=NF;i++) {
if($i ~ /old=[0-9]+K->[0-9]+K/) {
match($i, /old=([0-9]+)K->([0-9]+)K/, arr)
if(last_after > 0) {
growth = (arr[2] - last_after) / last_after * 100
printf("增长率: %.2f%% (从 %sK 到 %sK)\n", growth, last_after, arr[2])
}
last_after = arr[2]
}
}
}'# 3. 给出复现建议
echo -e "\n复现建议:"
echo "1. 使用以下JVM参数启动测试:"
echo " -Xmx512m -Xms512m -XX:+HeapDumpOnOutOfMemoryError"
echo "2. 模拟以下操作:"
echo " - 重复调用最近修改的接口"
echo " - 大数据量查询"
echo " - 文件上传下载"
第六步:创建修复方案
6.1 根据分析结果生成修复建议
#!/bin/bash
echo "=== 修复方案生成 ==="# 读取最大的类
TOP_CLASS=$(head -1 histo_by_size.txt | awk '{print $1}')echo "问题根因: 发现大量 $TOP_CLASS 对象"
case $TOP_CLASS in
*HashMap*)
echo "修复方案:"
echo "1. 检查静态HashMap是否无限增长"
echo "2. 添加LRU淘汰策略"
echo "3. 使用WeakHashMap或ConcurrentHashMap"
;;
*ArrayList*)
echo "修复方案:"
echo "1. 查询添加分页"
echo "2. 设置列表初始大小"
echo "3. 及时清理不再使用的列表"
;;
*byte*)
echo "修复方案:"
echo "1. 大文件使用流处理"
echo "2. 复用ByteBuffer"
echo "3. 限制上传文件大小"
;;
*)
echo "修复方案:"
echo "1. 检查 $TOP_CLASS 对象的创建位置"
echo "2. 查看是否有循环创建或静态引用"
echo "3. 添加对象池或复用机制"
;;
esac
第七步:创建完整报告
#!/bin/bash
REPORT="memory_issue_report_$(date +%Y%m%d_%H%M%S).txt"{
echo "内存问题分析报告"
echo "========================"
echo "生成时间: $(date)"
echoecho "一、问题现象"
echo "------------"
echo "服务挂掉时间: $(tail -1 gc.log | awk '{print $1}')"
echo "错误信息:"
grep "OutOfMemoryError" gc.log | tail -1
echoecho "二、GC日志分析"
echo "------------"
echo "1. Full GC次数: $(grep -c "Full GC" gc.log)"
echo "2. 最后5次Full GC:"
grep "Full GC" gc.log | tail -5 | awk '{print " " $0}'
echoecho "三、堆转储分析"
echo "------------"
echo "1. 堆转储文件大小: $(du -h heap_dump.hprof | awk '{print $1}')"
echo "2. 内存占用TOP 5:"
head -5 histo_by_size.txt | awk '{print " " $0}'
echoecho "四、问题根因"
echo "------------"
echo "主要问题类: $(head -1 histo_by_size.txt | awk '{print $1}')"
echo "实例数量: $(head -1 histo_by_size.txt | awk '{print $2}')"
echo "占用内存: $(head -1 histo_by_size.txt | awk '{print $3/1024}') MB"
echoecho "五、修复建议"
echo "------------"
# 根据TOP类给出建议
TOP_CLASS=$(head -1 histo_by_size.txt | awk '{print $1}')
if [[ $TOP_CLASS == *HashMap* ]]; then
cat << EOF
1. 代码层面:
- 检查所有静态HashMap,添加大小限制
- 使用Guava Cache或Caffeine替代
- 添加缓存淘汰策略2. 配置层面:
- 调整JVM参数: -XX:NewRatio=3
- 设置合理堆大小
- 启用G1垃圾回收器3. 监控层面:
- 添加缓存命中率监控
- 设置内存使用告警阈值
EOF
fiecho
echo "六、验证方法"
echo "------------"
echo "1. 本地复现: 使用相同JVM参数启动,模拟用户操作"
echo "2. 性能测试: 使用jmeter压测,观察内存增长"
echo "3. 监控验证: 修复后持续监控老年代使用率"} > $REPORT
echo "报告已生成: $REPORT"
快速排查流程图
开始
↓
1. tail -50 gc.log # 看死亡时刻
↓
2. jmap -histo heap_dump.hprof > histo.txt # 生成对象清单
↓
3. sort -k4 -rn histo.txt | head -20 # 找最大的对象
↓
4. 判断对象类型:
- 如果是byte[] → 检查文件/网络操作
- 如果是HashMap → 检查缓存代码
- 如果是自定义类 → grep搜索该类
↓
5. 结合gc.log看增长趋势 # 确认是否泄漏
↓
6. 修复并验证
最重要的三条命令
# 1. 查看死亡瞬间
tail -100 gc.log | grep -B5 -A5 "OutOfMemoryError\|Full GC"# 2. 找到罪魁祸首
jmap -histo heap_dump.hprof | sort -k4 -rn | head -20# 3. 定位到代码
grep -r "可疑类名" /path/to/project --include="*.java"
记住这个排查口诀:
-
看日志(怎么死的)
-
找对象(谁占的内存)
-
搜代码(谁创建的)
-
验增长(是否持续增长)
按照这个流程,即使只有命令行,30分钟内也一定能找到问题根源!
1万+

被折叠的 条评论
为什么被折叠?



