FullGC 多久一次正常?

#Java#面试八股

FullGC 多久一次正常?

结论先行:没有统一的「正常频率」,判断标准是趋势与停顿占比,而不是次数。一个健康系统通常天级甚至周级才出现一次 Full GC;如果每小时都有,就要查原因;如果每分钟都有,基本可以确定存在泄漏或元空间膨胀。真正该盯的指标是「Full GC 停顿时间占运行时间的比例」,一般要求低于 0.1%。

一、按频率判断

频率判断常见原因
数天 / 数周一次正常老年代自然填满或元空间缓慢增长
每天数次可接受,需观察缓存预热、大促流量峰值、晋升速率偏高
每小时数次异常老年代增长快、Survivor 过小导致过早晋升
每分钟数次严重内存泄漏、显式 System.gc()、元空间不足反复扩容
持续循环灾难堆几乎全是存活对象,GC 无效

二、Full GC 的触发来源

Full GC 触发
├── 老年代空间不足(晋升失败 / 担保失败)
├── 元空间(Metaspace)达到阈值需要扩容
├── 显式调用:System.gc()、Runtime.getRuntime().gc()
├── CMS 的并发模式失败(concurrent mode failure)
├── G1 的转移失败(evacuation failure)
├── 堆转储(jmap -dump / HeapDumpBeforeFullGC)
└── 直接内存不足触发的 System.gc()(DirectByteBuffer 清理)

三、用日志算频率

# 统计 Full GC 次数、总停顿、单次最长停顿
grep -E "Pause Full|Full GC" /var/log/app/gc.log \
  | awk '{for(i=1;i<=NF;i++) if($i ~ /ms$/) print $(i-1), $i}' \
  | awk '{n++; sum+=$1; if($1>max) max=$1} END {printf "次数=%d 总停顿=%dms 均值=%.1fms 最长=%dms\n", n, sum, sum/n, max}'

# 每秒采样一次,观察老年代占用与 FGC 计数
jstat -gcutil 1234 1000 20
# 关注列:O(老年代使用率)、FGC(Full GC 次数)、FGCT(累计停顿秒)

# 直接看所有 GC 原因的累计次数(JDK 11+)
jcmd 1234 GC.cause

jstat 输出里如果 O 长期停在 95% 以上、FGC 每轮都涨,且 FGCT 同步增长,就是「回收不动」的典型信号。

四、把 Full GC 打回原形

public class FullGcDemo {
    private static final java.util.List<byte[]> HOLD = new java.util.ArrayList<>();

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            HOLD.add(new byte[4 * 1024 * 1024]);      // 持续持有,逼近 -Xmx
            System.out.println("allocated " + (i + 1) * 4 + "MB");
        }
        System.gc();                                   // 显式触发,一次就能在日志里看到 Pause Full
    }
}
// 运行:java -Xmx64m -Xlog:gc FullGcDemo

五、常见追问

追问回答要点
元空间为什么会触发 Full GCMetaspaceSize 是首次扩容阈值,达到后先做一次 Full GC 尝试回收,回收不了才扩容
System.gc() 怎么查来源-XX:+ExplicitGCInvokesConcurrent 改为并发;用 -Xlog:gc+heap=debug 或 Arthas stack 抓调用栈
CMS 的并发模式失败是什么并发清理还没结束老年代就满了,只能退化为单线程 Full GC,停顿极长
G1 也会 Full GC 吗会,转移失败或 Humongous 分配失败时会退化为 Serial Full GC,这是 G1 需要重点监控的场景
Full GC 后老年代不降说明什么说明对象真的还活着:要么是长生命周期缓存,要么是泄漏,需要 dump 确认

六、日志配置别忘版本差异

目的JDK 8JDK 9+
打开 GC 日志-XX:+PrintGCDetails -XX:+PrintGCDateStamps-Xlog:gc*
输出到文件并滚动-Xloggc:/var/log/gc.log -XX:+UseGCLogFileRotation-Xlog:gc*:file=/var/log/gc.log:filesize=50m,filecount=5
记录 GC 原因无对应开关,靠日志文本判断-Xlog:gc+heap=debug、jcmd GC.cause
记录停顿细节-XX:+PrintGCApplicationStoppedTime-Xlog:safepoint

没有日志就没有「多久一次」这个问题的答案。很多团队只在事故后才想起加参数,结果重启丢失现场,只能猜。

小结

别背「多久一次」,要会解释「为什么是这个频率」。把 Full GC 次数、停顿、老年代回收率三组数据一起看,再对着触发来源逐项排除,结论自然清楚。

笔记加载中…