JVM通过分代设计、算法适配与运行时整理协同解决内存碎片:复制类回收器天然无碎片;标记-清除类易产生碎片;标记-整理类及G1的Region分区可有效压缩或绕过碎片问题。

JVM 解决 Java 内存碎片问题,核心在于“分代+算法适配+运行时整理”,不是靠单一手段,而是通过回收器选型、堆结构设计和对象生命周期管理协同作用。
回收器决定碎片处理能力的底层逻辑
不同垃圾回收器对碎片的应对方式本质不同:
- 复制类(如 Serial、Parallel Scavenge 年轻代,G1 年轻代):天然无碎片。每次 GC 把存活对象复制到新区域,原区域整块清空,分配靠指针碰撞,高效且连续。
- 标记-清除类(如 CMS):不移动对象,只清理死亡对象,必然留下大量不连续空闲块。老年代一旦碎片化,大对象无法分配,直接触发 Full GC 或 OOM。
- 标记-整理类(如 Parallel Old、G1 混合回收、ZGC、Shenandoah):在回收同时移动存活对象,压缩内存空间。G1 通过 Region 间复制 Evacuation 实现局部整理;ZGC 和 Shenandoah 在应用线程运行中并发整理,几乎消除碎片且停顿极低。
Region 分区让碎片“不可见”——G1 的关键突破
G1 不追求老年代整体连续,而是把堆划分为固定大小(默认 1–32MB)的 Region。分配对象时,只要总空闲 Region 数量足够,哪怕它们分散,也能满足需求。这从根本上绕开了“必须找一块连续大空间”的限制。
但要注意:G1 并非完全免疫碎片。当大量中等大小对象长期存活,导致多个 Region 都只剩零散空闲(例如每个剩 1–2MB),而新对象需要 4MB 连续空间时,就会出现 “to-space exhausted” 日志——说明目标 Region 没有足够整块空间接收复制对象,这是 G1 碎片的明确信号。
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
立即学习“Java免费学习笔记(深入)”;
参数与代码配合降低碎片源头
再好的回收器也需合理配置和编码习惯支撑:
- 用 -XX:PretenureSizeThreshold 控制大对象直接进老年代,避免其在年轻代反复复制加剧 Survivor 区压力和提前晋升。
- 调大 -XX:SurvivorRatio(如设为 8),增加 Survivor 容量,减少因空间不足导致的过早晋升,从源头减少老年代碎片来源。
- 设置合理 -XX:MaxTenuringThreshold(常设 3–6),防止中年对象在 Survivor 区无效循环,缩短晋升路径。
- 避免频繁创建短命大数组或 ByteBuffer.allocateDirect,这类对象易直接入老年代或堆外,加剧碎片或引发堆外泄漏。
诊断碎片不能只看堆大小,要看分配行为
判断是否真由碎片引起,关键看 GC 日志模式:
- G1 出现 to-space exhausted 或 Mixed GC 中 Free CSet 远小于 Total CSet,说明它在凑 Region 却凑不出理想收益,是典型碎片迹象。
- CMS 出现 concurrent mode failure 后接 Abort Preclean 或退化为 Serial Old,基本可断定老年代已严重碎片化。
- jstat 观察 MGCC(Mixed GC 次数)远高于 CCSC(Concurrent Cycle 次数),说明 G1 正被碎片频繁驱动被动回收。

















