g1不靠copying算法对抗老年代碎片,因其copying仅限young gc和mixed gc中的部分region复制,而非全局整理;碎片控制依赖region分区、rset和混合回收策略,真正全局整理仅由full gc完成。

G1 的 Copying 算法本身不直接用于老年代碎片整理,Stop-The-World 停顿主要来自 Mixed GC 中的复制阶段,而非“解决碎片”的独立动作。
为什么 G1 不靠 Copying 算法对抗老年代碎片?
G1 的“Copying”只发生在 Young GC 和 Mixed GC 的回收阶段,对象从 Eden/Survivor 或部分 Old Region 复制到空闲 Region。它确实能天然压缩内存(因为复制后源 Region 整体清空),但这不等于主动整理整个老年代的碎片。G1 通过 Region 分区 + RSet + 混合回收策略,把“碎片控制”拆解为局部行为:
- 每个 Region 是固定大小(如 2MB),分配时按需取整,避免传统连续堆中细碎空洞
- Humongous Region 专管大对象,防止巨型对象割裂老年代空间
- Mixed GC 只选“回收价值高、成本低”的老年代 Region 加入 CSet,不是全量扫描或整理
- 真正意义上的全局碎片整理,只有 Full GC 才做——而 G1 的设计目标就是尽量避免 Full GC
Stop-The-World 停顿在 Mixed GC 中到底花在哪?
你看到的 STW 时间,绝大部分消耗在 Initial Mark 和 Remark 阶段,而不是复制本身。复制(Copying)阶段虽然也 STW,但实际耗时通常很短,除非出现以下情况:
-
CSet中包含大量存活对象(比如老年代 Region 存活率 >90%),导致复制数据量暴增 - 跨 Region 引用极多,
RSet扫描和更新压力大,拖慢Remark - Humongous Region 被纳入回收,而它可能横跨多个连续 Region,复制/清理逻辑更重
- 堆太大(如 >64GB)但
-XX:G1HeapRegionSize仍用默认值,导致 Region 数过多,RSet 元数据膨胀
例如:堆设为 64GB,默认 Region 数 ≈2048 → Region 大小≈32MB;若没调大 Region,反而生成更多小 Region,RSet 管理开销上升,Remark 时间拉长。
如何定位 Copying 相关 STW 是否异常?
别只看总停顿时间,要拆解 GC 日志里的各阶段耗时。启用关键参数获取明细:
-
-Xlog:gc*,gc+phases=debug(JDK 10+)或-XX:+PrintGCDetails -XX:+PrintGCTimeStamps(旧版) - 重点关注日志中是否出现
[GC pause (G1 Evacuation Pause) (young)或(mixed),及其子项:initial-mark、evacuate、remark、cleanup - 如果
evacuate耗时占比突然升高(比如 >40% 总 STW),说明复制阶段成了瓶颈,大概率是 CSet 里老年代 Region 存活对象过多,或 Survivor 空间不足导致对象提前晋升
典型异常日志片段:[evacuate: 123.4ms] 占了整个 [GC pause (mixed): 156.7ms] 的近 80%,这时就要查晋升率和 -XX:G1MaxNewSizePercent 是否压得太死。
真正影响大规模堆下 STW 的隐藏因素
很多人盯着复制算法,却忽略更关键的底层约束:
-
RSet的维护依赖写屏障(Write Barrier),高并发写操作越多,Refine 线程越忙;若 Refine 跟不上,会触发同步修正,延长Remark -
-XX:MaxGCPauseMillis设得太低(如 50ms),G1 会强行缩小 CSet,导致回收不彻底,下次 Mixed GC 更频繁,STW 次数增加 - 未禁用
-XX:+UseStringDeduplication等额外功能时,字符串去重也会在Remark阶段加锁扫描,放大停顿 - 系统级干扰:CPU 抢占、内存页换入换出(尤其容器环境限制 memory.limit_in_bytes 后发生 swap)会让任何 STW 阶段“感觉更长”
超大堆(>128GB)下,哪怕单次 evacuate 只有 20ms,若每分钟触发 20 次 Mixed GC,累积停顿就不可忽视——这时问题已不在算法,而在回收节奏是否匹配业务对象生命周期。










