一次真实 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,生产环境仍然推荐将 XmsXmx 设为相同值,以避免运行期扩容和预测失败带来的系统风险。

相关推荐
猫头虎16 小时前
如何排查并解决项目启动时报错Error encountered while processing: java.io.IOException: closed 的问题
java·开发语言·jvm·spring boot·python·开源·maven
wgslucky16 小时前
jdk17 配置jvm参数中gc的日志及控制日志数量和大小
jvm·gc·-xlog
痴儿哈哈21 小时前
自动化机器学习(AutoML)库TPOT使用指南
jvm·数据库·python
野犬寒鸦1 天前
从零起步学习并发编程 || 第七章:ThreadLocal深层解析及常见问题解决方案
java·服务器·开发语言·jvm·后端·学习
闻哥1 天前
Kafka高吞吐量核心揭秘:四大技术架构深度解析
java·jvm·面试·kafka·rabbitmq·springboot
星辰_mya1 天前
Elasticsearch线上问题之慢查询
java·开发语言·jvm
蓝帆傲亦1 天前
代码革命!我用Claude Code 3个月完成1年工作量,这些实战经验全给你
jvm·数据库·oracle
Codiggerworld2 天前
JVM内存模型——你的对象住在哪里?
jvm
马猴烧酒.2 天前
【面试八股|JVM虚拟机】JVM虚拟机常考面试题详解
jvm·面试·职场和发展
2301_790300962 天前
Python数据库操作:SQLAlchemy ORM指南
jvm·数据库·python