一次真实 GC 实验:Parallel 与 G1 在 `Xms < Xmx` 下的日志对比分析

文章目录

  • 一、实验环境说明
  • 二、实验代码(持续制造老年代压力)
  • [三、Parallel GC:扩容 = Full GC(非常直接)](#三、Parallel GC:扩容 = Full GC(非常直接))
  • [四、G1 GC:扩容"更温和",但不是没有代价](#四、G1 GC:扩容“更温和”,但不是没有代价)
  • [五、Parallel vs G1:日志级别对比总结](#五、Parallel vs G1:日志级别对比总结)
  • [六、为什么两种 GC 都推荐 Xms = Xmx](#六、为什么两种 GC 都推荐 Xms = Xmx)

很多 JVM 文章都会告诉你:
生产环境要设置 -Xms = -Xmx

但很少有人用真实 GC 日志 告诉你:

如果不这么做,Parallel 和 G1 分别会发生什么?

本文基于 Java 17 ,使用同一份代码、同一组内存参数 ,

对比 Parallel GC 与 G1 GC 在 Xms < Xmx 场景下的真实行为差异。


一、实验环境说明

JVM 版本

text 复制代码
JDK 17.0.7

JVM 参数(仅 GC 不同)

Parallel GC

bash 复制代码
-Xms16m -Xmx64m -Xmn8m
-XX:+UseParallelGC
-Xlog:gc*,gc+heap=info

G1 GC

bash 复制代码
-Xms16m -Xmx64m
-XX:+UseG1GC
-Xlog:gc*,gc+heap=info

二、实验代码(持续制造老年代压力)

java 复制代码
public class HeapExpansionFullGCDemo {

    static class BigObject {
        byte[] data = new byte[1024 * 1024]; // 1MB
    }

    public static void main(String[] args) {
        List<Object> holder = new ArrayList<>();
        while (true) {
            holder.add(new BigObject());
        }
    }
}

代码特征

  • 持续创建 1MB 大对象
  • 使用强引用保存
  • 极易触发老年代压力
  • 在 G1 下属于 Humongous Object

三、Parallel GC:扩容 = Full GC(非常直接)

1️⃣ 初始阶段:堆大小固定在 Xms

text 复制代码
GC(0) Pause Young (Allocation Failure)
5M->4M(15M)

说明此时堆容量 ≈ 16MB(Xms)


2️⃣ 第一次扩容:伴随 Full GC(关键证据)

text 复制代码
GC(1) Pause Full (Ergonomics)
ParOldGen: 4104K(8192K)->4867K(19968K)
4M->4M(26M)

三个铁证:

  1. 老年代容量:8MB → 19MB
  2. 堆总容量:16MB → 26MB
  3. Full GC 原因:Ergonomics

👉 说明 JVM 在运行期扩容,并且扩容必须通过 Full GC 完成


3️⃣ 多次扩容 + 多次 Full GC

随着对象持续创建:

text 复制代码
ParOldGen: 15107K(19968K)->15107K(36864K)
Pause Full (Ergonomics)
text 复制代码
ParOldGen: 35588K(36864K)->35588K(57344K)
Pause Full (Ergonomics)

特点非常明显:

  • 每次扩容
  • 都伴随 Full GC
  • Full GC 成为"常规操作"

4️⃣ 最终结局:Allocation Failure + OOM

text 复制代码
Pause Full (Allocation Failure)
ParOldGen used 99%
java.lang.OutOfMemoryError

Parallel GC 的完整路径

复制代码
Xms < Xmx
→ 运行期扩容
→ 扩容 = Full GC
→ Full GC 频繁
→ 老年代耗尽
→ OOM

四、G1 GC:扩容"更温和",但不是没有代价

1️⃣ 一开始就不一样:Humongous Object

text 复制代码
Heap Region Size: 1M
Pause Young (G1 Humongous Allocation)
Humongous regions: 8->8

说明:

  • 1MB 对象 ≥ 0.5 Region
  • 直接进入 Humongous Region
  • 直接占用 Old Region

2️⃣ G1 的扩容方式:Region 逐步启用

你在日志中看到的是:

text 复制代码
9M->8M(16M)
14M->14M(29M)
34M->34M(62M)
58M->58M(64M)

而不是:

text 复制代码
Heap expanded from A to B

这意味着:

  • G1 在运行期逐步启用 Region
  • 堆容量"平滑增长"
  • 没有立即触发 Full GC

👉 这正是 G1 相比 Parallel 的优势


3️⃣ 前期:只有 Young / Concurrent GC,没有 Full GC

text 复制代码
Pause Young (G1 Evacuation Pause)
Concurrent Mark Cycle
Pause Remark
Pause Cleanup

G1 通过并发标记和预防性回收 成功顶住压力。


4️⃣ 真正的转折点:Humongous Region 失控

text 复制代码
Humongous regions: 60->60

含义:

  • 约 60MB 的堆
  • 几乎全被 Humongous Object 占满
  • Old Region 严重碎片化

5️⃣ G1 宣布"兜底":Full GC(Compaction)

text 复制代码
Attempting full compaction
Pause Full (G1 Compaction Pause)

随后:

text 复制代码
Attempting maximum full compaction clearing soft references
Pause Full (G1 Compaction Pause)

这是 G1 的最后手段。


6️⃣ 最终结果:Full GC 失败 → OOM

text 复制代码
java.lang.OutOfMemoryError: Java heap space

G1 的完整路径

复制代码
Xms < Xmx
→ Region 逐步扩容
→ 并发 / Mixed GC
→ Humongous Region 占满
→ G1 Compaction Full GC
→ OOM

五、Parallel vs G1:日志级别对比总结

对比项 Parallel GC G1 GC
扩容方式 整堆重布局 启用更多 Region
扩容是否 STW 是 是(但更轻)
扩容是否触发 Full GC 几乎必然 通常不会
Full GC 角色 常规手段 最终兜底
Humongous 对象 无特殊处理 极易致命
OOM 前信号 多次 Full GC Concurrent → Compaction

六、为什么两种 GC 都推荐 Xms = Xmx

基于这两份真实日志,可以得出一个非常清晰的结论:

  • 在 Parallel GC 下,Xms < Xmx 几乎等价于
    运行期 Full GC 风险
  • 在 G1 GC 下,虽然扩容更温和,
    但在高分配速率或 Humongous Object 场景中,
    仍然可能退化为 Compaction Full GC

👉 因此,即使使用 G1,生产环境仍然推荐将 Xms 与 Xmx 设为相同值,以避免运行期扩容和预测失败带来的系统风险。

相关推荐
用户094248568035 小时前
第22章:JIT编译分层——Interpreter、C1、C2与热点探测
java·jvm
乌暮6 小时前
JVM 原理与实践:从运行时数据区到线上故障排查
java·开发语言·jvm·后端·学习·面试
长河1 天前
Java的GC(垃圾回收)中,标记无用对象的核心方法是可达性分析算法
java·jvm·算法
可乐ea1 天前
GitHub Security Lab 开源 Fuzzing Taskflow:让 LLM 智能体自己跑完 C/C++ 模糊测试
jvm·c++·github·模糊测试·mcp·代码agent·智能体自动化
一条破秋裤1 天前
Linux 线程同步:读写锁
java·linux·jvm
wuminyu1 天前
JEP491中synchronized关键字引发的平台线程钉住解决方法简介
java·linux·c语言·jvm·c++
一条破秋裤1 天前
Linux 线程同步:条件变量
java·linux·jvm
谢亮_vipxieliang2 天前
GC 入门:G1 与 ZGC 怎么选
java·jvm·算法
小溪学编程2 天前
从 C++ 的规范变迁看语言发展
jvm·c++·面试
晚安code2 天前
JVM 类加载机制详解:双亲委派模型的原理、优势与三种打破方式
jvm