新生代垃圾回收采用复制算法,按eden:s0:s1=8:1:1布局,新对象入eden,存活对象在survivor间复制,达年龄阈值或survivor空间不足时晋升老年代,需依监控调优比例与阈值。

新生代垃圾回收采用复制算法,核心目标是快速清理大量短命对象,同时避免内存碎片。它不是靠“猜”或“调大堆”,而是围绕内存布局、对象生命周期和GC行为做精准适配。
理解Eden与Survivor的分工逻辑
HotSpot不把新生代简单均分为两块,而是按 Eden : S0 : S1 = 8 : 1 : 1 划分。新对象全部进入Eden;Minor GC时,存活对象先被复制到一个Survivor区(比如S0),另一Survivor(S1)清空;下一次GC时角色互换。这种设计兼顾了空间效率和复制成本——Eden占大头,保证多数对象“出生即死”,不用搬运;Survivor留出缓冲,让中等寿命对象多活几轮。
关键点:
- Survivor区太小(如-XX:SurvivorRatio设为15),会导致对象频繁“躲不过一轮GC”就直接晋升老年代,加剧老年代压力;
- Survivor太大(如设为2),虽减少晋升,但复制开销上升,且Eden变小后Minor GC更频繁;
- 默认比例已适配多数场景,除非监控发现
YGC后Survivor占用率长期超70%或老年代晋升速率异常高,否则不必盲目调整。
控制对象晋升节奏,别让Survivor“超载”
对象在Survivor间来回复制是有次数上限的,默认最多15次(由-XX:MaxTenuringThreshold控制)。达到阈值就晋升老年代。但实际晋升往往更早发生——当某次GC后,Survivor放不下所有存活对象时,JVM会把“放不下的那部分”直接送进老年代,这叫动态年龄判定。
这意味着:晋升不是只看年龄,更取决于Survivor空间是否够用。因此:
- 若观察到
Full GC频繁但老年代增长缓慢,可能是Survivor太小导致“被动晋升”过多; - 可适当调高-XX:MaxTenuringThreshold(如设为15),但前提是Survivor有足够余量容纳多次复制的对象;
- 更稳妥的做法是结合-XX:TargetSurvivorRatio(如设为90),让JVM尽量填满Survivor再晋升,减少无效搬运。
避免人为干扰复制过程的连续性
复制算法依赖“From区全清空、To区全接管”的原子性。如果代码中存在大量长生命周期的临时对象(比如缓存Map、未关闭的流、静态集合误存局部对象),它们会在Minor GC中反复存活,被迫多次复制,拖慢GC速度,还可能挤占真正该留下的对象空间。
实践中要注意:
- 方法内创建的大数组、StringBuilder、ByteBuffer等,用完及时置null或复用,别依赖GC“善后”;
- 避免在循环中反复new相同结构的对象(如DTO、VO),优先考虑对象池或复用机制;
- 检查日志或堆转储(heap dump),确认Survivor中是否存在本该属于老年代的“稳定对象”,它们的存在会降低复制效率。
监控才是调优的起点,不是参数堆砌
复制算法本身无需手动开启——只要用的是主流回收器(Serial、Parallel、G1、ZGC),新生代默认就是复制式回收。真正需要关注的是它的运行效果:
- 用
jstat -gc <pid></pid>看EC(Eden容量)、EU(Eden使用量)、YC(Young GC次数)、YGCT(Young GC耗时); - 重点关注
YGC频率是否陡增(可能Eden太小或对象创建过快)、每次YGC后Eden是否几乎清零(说明回收有效)、S0U/S1U是否长期非零且波动小(说明对象在Survivor中“稳住”了); - 配合GC日志(-Xlog:gc*,gc+age=debug)看对象年龄分布,比盲目调-XX:MaxTenuringThreshold更有依据。











