j-1 jstat -gcutil 命令详解

一、jstat -gcutil 命令详解

1.1 命令格式和参数

jstat -gcutil <pid> <interval> <count>
# 示例:jstat -gcutil 12345 1000 10
# 每1秒采样一次,共采样10次

1.2 输出结果示例

  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
  0.00  99.73  79.06  42.63  95.58  91.67   1441   23.232    12    1.234   24.466
  0.00  99.73  79.06  42.63  95.58  91.67   1441   23.232    12    1.234   24.466

1.3 各列详细解释

列名 全称 说明 正常范围 异常判断
S0 Survivor 0 第一个Survivor区使用百分比 0-100% 持续100%可能对象晋升慢
S1 Survivor 1 第二个Survivor区使用百分比 0-100% 持续100%可能对象晋升慢
E Eden Eden区使用百分比 0-100% 增长过快可能创建大量临时对象
O Old 老年代使用百分比 <80% >80%可能触发Full GC
M Metaspace 元空间使用百分比 <90% >90%可能Metaspace OOM
CCS Compressed Class Space 压缩类空间使用百分比 <90% >90%需关注
YGC Young GC Count 年轻代GC次数 - 增长过快可能内存泄漏
YGCT Young GC Time 年轻代GC总时间(秒) - YGCT/YGC > 0.1秒需关注
FGC Full GC Count Full GC次数 越少越好 >1次/小时需关注
FGCT Full GC Time Full GC总时间(秒) 越少越好 FGCT/FGC > 1秒需优化
GCT GC Total Time GC总时间(秒) - GCT/运行时间 > 5%需优化

1.4 实战分析案例

案例1:Eden区增长过快

S0   S1   E    O    M   CCS  YGC  YGCT FGC FGCT  GCT
0.0 100.0 95.3 45.2 78.4 82.1 152 3.456 5 0.987 4.443
0.0 100.0 98.7 45.2 78.4 82.1 153 3.478 5 0.987 4.465
0.0 0.0   12.3 45.8 78.4 82.1 154 3.512 5 0.987 4.499  # YGC+1,Eden清空

分析:Eden区从95.3%快速到98.7%,触发YGC,对象晋升到Old区(O从45.2%到45.8%)

案例2:频繁Full GC

S0   S1   E    O    M   CCS  YGC  YGCT FGC FGCT  GCT
0.0 100.0 34.2 89.7 62.1 58.9 245 8.912 23 15.678 24.590
0.0 0.0   12.5 89.8 62.1 58.9 246 8.945 23 15.678 24.623
0.0 100.0 56.7 89.9 62.1 58.9 247 8.978 24 16.123 25.101  # FGC+1

分析:老年代使用率89.7%接近Full GC阈值,频繁Full GC(FGCT高达15.678秒)

案例3:Survivor区异常

S0   S1   E    O    M   CCS  YGC  YGCT FGC FGCT  GCT
100.0 0.0 45.6 23.4 34.5 29.8 123 2.345 0 0.000 2.345
100.0 0.0 67.8 23.4 34.5 29.8 123 2.345 0 0.000 2.345
100.0 0.0 89.2 23.4 34.5 29.8 123 2.345 0 0.000 2.345

分析:S0持续100%,S1持续0%,说明对象在Survivor区之间没有正常交换

二、jstat -gc 命令详解

2.1 命令格式和参数

jstat -gc <pid> <interval> <count>
# 示例:jstat -gc 12345 1000 5
# 每1秒采样一次,共采样5次

2.2 输出结果示例(G1 GC)

 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
 0.0   1024.0  0.0   1024.0  8192.0   8192.0   10240.0    5120.0   105676.0 102400.0 14080.0 12800.0   1441   23.232    12    1.234   24.466

2.3 各列详细解释(容量单位为KB)

年轻代相关

列名 全称 说明 计算公式 监控要点
S0C Survivor 0 Capacity S0区总容量(KB) - 与-Xmn比例相关
S1C Survivor 1 Capacity S1区总容量(KB) - 通常等于S0C
S0U Survivor 0 Used S0区已使用(KB) - S0U/S0C = gcutil的S0
S1U Survivor 1 Used S1区已使用(KB) - S1U/S1C = gcutil的S1
EC Eden Capacity Eden区总容量(KB) - 年轻代主要部分
EU Eden Used Eden区已使用(KB) - EU/EC = gcutil的E

老年代相关

列名 全称 说明 计算公式 监控要点
OC Old Capacity 老年代总容量(KB) - 堆大小 - 年轻代大小
OU Old Used 老年代已使用(KB) - OU/OC = gcutil的O

元空间相关

列名 全称 说明 计算公式 监控要点
MC Metaspace Capacity 元空间总容量(KB) - 受-XX:MaxMetaspaceSize限制
MU Metaspace Used 元空间已使用(KB) - MU/MC = gcutil的M
CCSC Compressed Class Space Capacity 压缩类空间容量(KB) - -XX:CompressedClassSpaceSize
CCSU Compressed Class Space Used 压缩类空间已使用(KB) - CCSU/CCSC = gcutil的CCS

GC统计

列名 全称 说明 单位
YGC Young GC Count 年轻代GC次数
YGCT Young GC Time 年轻代GC总时间
FGC Full GC Count Full GC次数
FGCT Full GC Time Full GC总时间
GCT GC Total Time GC总时间

2.4 不同GC收集器的输出差异

Parallel GC(默认)

 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
5120.0 5120.0  0.0   5120.0 32768.0  32768.0   65536.0   32768.0   32000.0 29999.0 4000.0 3800.0   100    5.123     5    2.456    7.579

G1 GC

 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
  0.0  1024.0  0.0   1024.0  8192.0   8192.0   10240.0    5120.0   105676.0 102400.0 14080.0 12800.0   1441   23.232    12    1.234   24.466

注意:G1的EC和OC是动态变化的,S0C/S1C可能为0

CMS GC

 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
2048.0 2048.0  0.0   2048.0 16384.0  16384.0   40960.0   20480.0   32000.0 29999.0 4000.0 3800.0   100    5.123     2    0.500    5.623

2.5 实战计算示例

# 示例数据:
# S0C=2048, S0U=0, S1C=2048, S1U=2048, EC=8192, EU=4096, OC=20480, OU=10240
# MC=32768, MU=16384, CCSC=4096, CCSU=2048, YGC=100, YGCT=5.0, FGC=2, FGCT=1.0

# 计算使用率
S0使用率 = S0U / S0C = 0 / 2048 = 0%
S1使用率 = S1U / S1C = 2048 / 2048 = 100%
Eden使用率 = EU / EC = 4096 / 8192 = 50%
Old使用率 = OU / OC = 10240 / 20480 = 50%
Metaspace使用率 = MU / MC = 16384 / 32768 = 50%
CCS使用率 = CCSU / CCSC = 2048 / 4096 = 50%

# 计算平均GC时间
平均YGC时间 = YGCT / YGC = 5.0 / 100 = 0.05秒
平均FGC时间 = FGCT / FGC = 1.0 / 2 = 0.5秒
GC总时间占比 = GCT / 进程运行时间

# 计算堆内存总量
总堆内存 = EC + S0C + S1C + OC = 8192 + 2048 + 2048 + 20480 = 32768KB = 32MB
已使用堆内存 = EU + S0U + S1U + OU = 4096 + 0 + 2048 + 10240 = 16384KB = 16MB
堆使用率 = 已使用堆内存 / 总堆内存 = 16MB / 32MB = 50%

三、常见问题诊断模式

3.1 内存泄漏诊断

模式识别:

# 连续采样观察
jstat -gcutil 12345 1000 10

# 输出示例(内存泄漏):
时间点1: O=45%, YGC=100, FGC=2
时间点2: O=55%, YGC=120, FGC=3  # Old区增长,YGC增加
时间点3: O=65%, YGC=140, FGC=5  # 持续增长
时间点4: O=75%, YGC=160, FGC=8  # FGC频率增加
时间点5: O=85%, YGC=180, FGC=12 # 接近OOM

分析:

  • Old区使用率持续上升
  • YGC次数增加但无法回收Old区对象
  • FGC频率增加,但回收效果差

3.2 年轻代配置不合理

模式识别:

# 年轻代过小
S0   S1   E    O    M   CCS  YGC  YGCT FGC FGCT  GCT
0.0 100.0 95.0 30.0 45.0 40.0 5000 250.0 50 25.0 275.0
# YGC次数极高(5000次),说明对象很快填满Eden

# 年轻代过大
0.0 100.0 10.0 80.0 45.0 40.0 50 30.0 10 10.0 40.0
# YGC次数少但每次时间长(平均0.6秒),老年代使用率高

3.3 Full GC频繁

诊断步骤:

# 1. 监控FGC增长
watch -n 1 'jstat -gcutil 12345 | awk "{print \$9, \$10, \$11}"'

# 2. 分析Full GC触发原因
# 原因1:Old区空间不足
# 原因2:System.gc()调用
# 原因3:Metaspace不足
# 原因4:Promotion Failed

# 3. 检查GC日志确认
tail -f gc.log | grep "Full GC"

3.4 元空间泄漏

模式识别:

S0   S1   E    O    M     CCS  YGC YGCT FGC FGCT GCT
0.0 100.0 45.6 23.4 95.8  92.1 123 2.345 0 0.000 2.345
0.0 100.0 56.7 23.4 96.2  92.5 124 2.378 0 0.000 2.378
0.0 100.0 67.8 23.4 96.8  93.0 125 2.412 0 0.000 2.412

分析: Metaspace(M)持续增长接近100%,可能动态生成类过多

四、自动化监控脚本

4.1 实时监控脚本

#!/bin/bash
# monitor_jstat.sh

PID=$1
INTERVAL=${2:-1000}
COUNT=${3:-10}

echo "开始监控进程 $PID,间隔 ${INTERVAL}ms,次数 $COUNT"
echo "时间                S0     S1     E      O      M     CCS   YGC   YGCT   FGC  FGCT    GCT"
echo "--------------------------------------------------------------------------------"

jstat -gcutil $PID $INTERVAL $COUNT | awk -v interval=$INTERVAL '
NR>1 {
    # 计算时间戳
    timestamp = strftime("%Y-%m-%d %H:%M:%S");
    
    # 判断异常并标记颜色
    color = "";
    reset = "";
    
    if ($3 > 90) color = "\033[33m";  # Eden > 90% 黄色警告
    if ($4 > 80) color = "\033[31m";  # Old > 80% 红色警告
    if ($5 > 90) color = "\033[35m";  # Metaspace > 90% 紫色警告
    if ($9 > 0)  color = "\033[41m\033[37m";  # 发生FGC 红底白字
    
    reset = "\033[0m";
    
    printf "%s %s%6.1f %6.1f %6.1f %6.1f %6.1f %6.1f %5d %7.3f %4d %6.3f %7.3f%s\n", 
        timestamp, color, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, reset;
}'

4.2 历史数据分析脚本

#!/bin/bash
# analyze_jstat_history.sh

LOG_FILE=$1
TARGET_PID=$2

echo "=== JStat历史数据分析报告 ==="
echo "分析文件: $LOG_FILE"
echo "目标PID: $TARGET_PID"
echo "生成时间: $(date)"
echo ""

# 提取并分析数据
awk -v pid="$TARGET_PID" '
BEGIN {
    print "时间范围                YGC 平均时间(s)   FGC 平均时间(s)  Old平均使用率 Eden峰值"
    print "--------------------------------------------------------------------------------"
}

/YGC.*YGCT.*FGC.*FGCT/ {
    # 跳过标题行
    next
}

{
    # 解析每行数据
    split($0, fields, " ");
    
    # 计算统计
    ygc_sum += fields[7];
    ygct_sum += fields[8];
    fgc_sum += fields[9];
    fgct_sum += fields[10];
    old_sum += fields[4];
    
    # 记录峰值
    if (fields[3] > eden_peak) eden_peak = fields[3];
    if (fields[4] > old_peak) old_peak = fields[4];
    
    count++;
    
    # 每10行输出一次统计
    if (count % 10 == 0) {
        avg_ygct = (ygct_sum - last_ygct) / (ygc_sum - last_ygc);
        avg_fgct = (fgct_sum - last_fgct) / (fgc_sum - last_fgc);
        avg_old = old_sum / 10;
        
        printf "%s-%s  %4d %8.3f      %3d %8.3f      %6.1f%%    %6.1f%%\n",
            start_time, fields[1],
            ygc_sum - last_ygc, avg_ygct,
            fgc_sum - last_fgc, avg_fgct,
            avg_old, eden_peak;
        
        last_ygc = ygc_sum;
        last_ygct = ygct_sum;
        last_fgc = fgc_sum;
        last_fgct = fgct_sum;
        old_sum = 0;
        start_time = fields[1];
        eden_peak = 0;
    }
}

END {
    print "\n=== 总结 ===";
    printf "总运行时间: %d次采样\n", count;
    printf "总YGC次数: %d,总时间: %.3fs,平均: %.3fs/次\n", 
        ygc_sum, ygct_sum, ygct_sum/ygc_sum;
    printf "总FGC次数: %d,总时间: %.3fs,平均: %.3fs/次\n", 
        fgc_sum, fgct_sum, fgct_sum/fgc_sum;
    printf "Old区平均使用率: %.1f%%,峰值: %.1f%%\n", 
        old_sum/count, old_peak;
    printf "Eden区峰值: %.1f%%\n", eden_peak;
    
    # 给出建议
    print "\n=== 优化建议 ===";
    if (fgct_sum/ygct_sum > 0.5) {
        print "⚠️  Full GC时间占比过高,建议:";
        print "    1. 检查内存泄漏";
        print "    2. 调整新生代与老年代比例";
        print "    3. 考虑使用G1 GC";
    }
    if (old_peak > 85) {
        print "⚠️  老年代使用率过高,建议:";
        print "    1. 增加堆内存(-Xmx)";
        print "    2. 降低对象晋升阈值(-XX:MaxTenuringThreshold)";
    }
    if (ygct_sum/ygc_sum > 0.1) {
        print "⚠️  Young GC时间过长,建议:";
        print "    1. 减小新生代大小";
        print "    2. 检查创建大量临时对象的代码";
    }
}' "$LOG_FILE"

4.3 告警触发脚本

#!/bin/bash
# jstat_alert.sh

PID=$1
THRESHOLD_OLD=${2:-80}  # 老年代阈值,默认80%
THRESHOLD_META=${3:-90} # 元空间阈值,默认90%
ALERT_INTERVAL=${4:-60} # 告警间隔,默认60秒

LAST_ALERT_TIME=0

while true; do
    # 获取jstat数据
    JSTAT_OUTPUT=$(jstat -gcutil $PID 1000 1 | tail -1)
    
    # 解析数据
    OLD_USAGE=$(echo $JSTAT_OUTPUT | awk '{print $4}')
    META_USAGE=$(echo $JSTAT_OUTPUT | awk '{print $5}')
    FGC_COUNT=$(echo $JSTAT_OUTPUT | awk '{print $9}')
    CURRENT_TIME=$(date +%s)
    
    # 检查条件并告警
    if (( $(echo "$OLD_USAGE > $THRESHOLD_OLD" | bc -l) )); then
        if (( CURRENT_TIME - LAST_ALERT_TIME > ALERT_INTERVAL )); then
            echo "[$(date)] ⚠️  告警:老年代使用率过高 - ${OLD_USAGE}% (阈值: ${THRESHOLD_OLD}%)"
            echo "进程: $PID,建议检查内存泄漏或增加堆内存"
            LAST_ALERT_TIME=$CURRENT_TIME
        fi
    fi
    
    if (( $(echo "$META_USAGE > $THRESHOLD_META" | bc -l) )); then
        if (( CURRENT_TIME - LAST_ALERT_TIME > ALERT_INTERVAL )); then
            echo "[$(date)] ⚠️  告警:元空间使用率过高 - ${META_USAGE}% (阈值: ${THRESHOLD_META}%)"
            echo "进程: $PID,建议增加-XX:MaxMetaspaceSize"
            LAST_ALERT_TIME=$CURRENT_TIME
        fi
    fi
    
    # 检查FGC次数突然增加
    if [ -f /tmp/last_fgc_count ]; then
        LAST_FGC=$(cat /tmp/last_fgc_count)
        if (( FGC_COUNT > LAST_FGC + 2 )); then
            echo "[$(date)] ⚠️  告警:短时间内发生多次Full GC"
            echo "当前FGC: $FGC_COUNT,上次: $LAST_FGC"
        fi
    fi
    echo $FGC_COUNT > /tmp/last_fgc_count
    
    sleep 5
done

五、与其它工具结合分析

5.1 结合jmap分析对象分布

# 当发现Old区使用率高时,分析对象分布
jmap -histo:live <pid> | head -20

# 输出示例:
 num     #instances         #bytes  class name
----------------------------------------------
   1:       1234567      1073741824  [B  # byte数组占用1GB
   2:        987654       268435456  [Ljava.lang.Object;  # Object数组
   3:        500000       134217728  java.lang.String

5.2 结合jstack分析线程

# 当GC时间过长时,检查GC线程
jstack <pid> | grep -A 5 "GC"

# 输出示例:
"GC Thread#0" #12 daemon prio=2 os_prio=0 cpu=1234.56ms elapsed=12345.67s

5.3 结合监控系统(Prometheus)

# 将jstat数据转换为Prometheus格式
- pattern: 'jvm\.gc\.(\w+)\.time:(\d+\.\d+)'
  name: jvm_gc_$1_seconds_total
  type: COUNTER
  help: Total time spent in $1 GC
  
- pattern: 'jvm\.gc\.(\w+)\.count:(\d+)'
  name: jvm_gc_$1_total
  type: COUNTER
  help: Total number of $1 GCs

六、最佳实践总结

6.1 监控要点

  1. 定期采样:生产环境建议每分钟采样一次,持久化存储
  2. 关注趋势:不仅看当前值,更要关注变化趋势
  3. 设置基线:为每个应用建立正常的GC行为基线
  4. 关联分析:结合业务指标(QPS、响应时间)分析

6.2 关键阈值建议

指标 警告阈值 严重阈值 行动建议
Old区使用率 >70% >85% 检查内存泄漏
Metaspace使用率 >80% >90% 增加MaxMetaspaceSize
YGC平均时间 >0.05s >0.1s 优化年轻代大小
FGC频率 >1次/小时 >1次/10分钟 紧急优化
GC时间占比 >5% >10% 全面GC调优

6.3 常见优化方向

  1. 调整堆大小-Xms-Xmx-Xmn
  2. 调整Survivor比例-XX:SurvivorRatio
  3. 调整晋升阈值-XX:MaxTenuringThreshold
  4. 更换GC算法:Parallel、CMS、G1、ZGC
  5. 优化代码:减少大对象、及时释放资源

通过熟练使用jstat命令,结合其他工具和监控系统,可以有效诊断和解决Java内存问题,保障应用稳定运行。

posted @ 2025-12-10 14:08  冰冷的火  阅读(224)  评论(0)    收藏  举报