老年代是Java堆中专门存放长期存活对象的内存区域,通常在Minor GC(新生代GC)后仍存活的对象(如经过多次GC仍存活的对象)会从新生代的Survivor区晋升至老年代,由于老年代存放的是生命周期较长的对象,其GC机制与新生代有显著差异,主要目标是回收不再使用的对象,同时尽量减少应用停顿时间,本文将从老年代GC的触发机制、主流算法、调优策略等方面展开分析。

老年代GC的触发机制
老年代GC的触发并非随机,而是由特定的内存分配条件和GC策略共同决定,核心触发场景包括以下几类:
空间分配担保失败
Minor GC前,JVM会检查老年代是否有足够空间容纳新生代所有存活对象,如果空间不足,JVM会触发一次Full GC(老年代GC),清理老年代以腾出空间,这是最常见的触发场景之一,尤其当老年代碎片化严重或内存不足时。
大对象直接进入老年代
通过参数-XX:PretenureSizeThreshold可以设置大对象阈值(如默认为0字节),超过该阈值的对象会直接在老年代分配,若老年代空间不足,分配时会立即触发Full GC。
老年代空间不足
若老年代剩余空间无法容纳新晋升的对象(如Minor GC后大量对象从Survivor区晋升),或动态年龄判定(相同年龄对象总和超过Survivor区一半)导致对象提前进入老年代,都会触发Full GC。
GC策略主动触发
部分GC算法(如CMS、G1)会根据运行状态主动触发并发GC,CMS在老年代使用率达到某个阈值(如-XX:CMSInitiatingOccupancyFraction设置)时,启动并发标记阶段;G1则基于Mixed GC策略,在回收大部分Region时触发老年代部分回收。
主流老年代GC算法解析
Java针对老年代GC设计了多种算法,各有侧重,适用于不同场景。

CMS(Concurrent Mark Sweep)
CMS是以“低停顿”为目标的老年代GC算法,基于“标记-清除”实现,主要分为四个阶段:
- 初始标记:标记GC Roots直接关联的对象,停顿时间短;
- 并发标记:遍历对象图进行标记,与应用线程并行;
- 重新标记:修正并发标记期间因应用线程变动导致的标记变动,停顿时间较短;
- 并发清除:清除标记对象,与应用线程并行。
优点:并发执行,大部分阶段与应用线程并行,停顿时间短;
缺点:基于“清除”会产生内存碎片,可能导致Concurrent Mode Failure(触发Serial Old GC,停顿时间变长);并发阶段会占用CPU资源,可能影响应用吞吐量。
G1(Garbage-First)
G1是JDK 9后的默认GC算法,将堆划分为多个大小相等的Region(通常1-32MB),每个Region可能是Eden、Survivor或Old,老年代的GC通过“混合回收”(Mixed GC)实现,同时清理部分新生代和老年代Region,核心步骤包括:
- 初始标记:与CMS类似,标记GC Roots直接关联对象;
- 并发标记:遍历对象图,记录可达对象;
- 最终标记:修正并发标记期间的变动;
- 筛选回收:根据回收价值(垃圾占比高、回收时间短)选择Region进行清理,优先回收“垃圾最多”的Region(Garbage-First)。
优点:可预测停顿时间(通过-XX:MaxGCPauseMillis设置),内存碎片化问题小(基于Region整理);支持大堆内存(堆大小可达数TB);
缺点:算法复杂,内存占用略高于CMS;并发阶段可能与应用线程竞争资源。
ZGC(Z Garbage Collector)
ZGC是JDK 11引入的低延迟GC,设计目标为“无论堆大小多大,停顿时间均不超过10毫秒”,核心技术包括:
- 染色指针:将对象指针的额外信息(如标记位、锁信息)存储在指针中,避免传统标记的内存开销;
- 并发处理:所有标记、转移阶段均与应用线程并发执行;
- 内存映射:通过读屏障(Load Barrier)和写屏障(Store Barrier)实现指针访问的实时检查。
优点:停顿时间极短,几乎不受堆大小影响;支持TB级内存;内存碎片化问题小;
缺点:并发阶段CPU占用较高;需要JDK 11+支持。

Serial Old / Parallel Old
Serial Old是单线程老年代GC,采用“标记-整理”算法,主要用于Client模式或作为CMS失败后的备用GC;Parallel Old是Parallel Scavenge的老年代版本,多线程执行“标记-整理”,注重吞吐量,适用于后台计算等对停顿不敏感的场景。
老年代GC调优实践
老年代GC调优的核心目标是平衡“吞吐量”(单位时间内处理任务量)和“延迟”(GC停顿时间),需结合业务场景和GC算法特性进行。
选择合适的GC算法
- 低延迟场景(如在线交易、实时交互):优先选择G1或ZGC,通过
-XX:+UseG1GC或-XX:+UseZGC启用; - 高吞吐量场景(如大数据计算):选择Parallel Old,通过
-XX:+UseParallelOldGC启用; - 兼容旧版本JDK:若使用JDK 8之前版本,CMS是低延迟的首选,但需关注碎片化问题。
关键参数调优
- 老年代大小:通过
-XX:OldSize和-XX:MaxOldSize设置固定大小,避免动态调整带来的性能波动; - GC触发阈值:G1可通过
-XX:InitiatingHeapOccupancyPercent设置Mixed GC触发阈值(默认45%),避免过早或过晚触发; - 线程数:并行GC可通过
-XX:ParallelGCThreads调整并行线程数(通常为CPU核心数);并发GC可通过-XX:ConcGCThreads调整并发线程数(通常为并行线程数的1/4)。
监控与问题定位
- 工具使用:通过
jstat -gcutil pid监控老年代使用率(Old Gen列),jstat -gccapacity pid查看老年代空间分配; - GC日志分析:通过
-Xlog:gc*=info:file=gc.log:time,uptime,level,tags记录GC日志,使用GCViewer、GCEasy等工具分析停顿时间、频率和回收效率; - 碎片化处理:CMS可通过
-XX:+UseCMSCompactAtFullCollection在Full GC时碎片整理(但会增加停顿时间),或设置-XX:CMSFullGCsBeforeCompaction控制整理频率。
老年代GC是Java内存管理的核心环节,其性能直接影响应用的稳定性与用户体验,从早期的Serial Old到现代的G1、ZGC,GC算法不断优化,以适应低延迟、大堆内存的需求,在实际应用中,需结合业务场景选择合适的GC算法,通过参数调优和监控分析持续优化,最终实现吞吐量与延迟的平衡,随着云原生和Serverless架构的发展,更轻量、更智能的GC机制(如Shenandoah GC的进一步优化)将成为Java性能提升的重要方向。















