彻底搞懂 Young GC 与 Full GC:GC 日志逐行拆解 + 面试考点

📅 发布时间:2026/7/9 11:40:57 👁️ 浏览次数:
彻底搞懂 Young GC 与 Full GC:GC 日志逐行拆解 + 面试考点
前言上周朋友面试复盘的时候问JVM 的 Young GC 和 Full GC 到底有什么本质区别为什么有的大对象会直接进入老年代GC 日志到底该怎么看懂以前只停留在“年轻代垃圾回收快、老年代回收慢”的浅层认知既看不懂真实的 GC 日志也搞不懂对象晋升、空间担保、动态年龄判断这些核心底层逻辑面试被深挖就翻车线上遇到 GC 卡顿、OOM 问题更是无从排查。实际上Java 的内存回收核心就是分代回收思想。绝大多数日常触发的 GC 都是 Young GC频次高、速度快对系统几乎无影响而一旦频繁触发 Full GC大概率就是程序内存泄漏、对象晋升异常导致的性能隐患。为了彻底搞懂这一块我结合完整 GC 日志逐行解析 高频面试考点从零拆解 JVM 分代垃圾回收的核心原理。一、什么是Young GC和Full GCJava 对象通常优先在新生代的 Eden 区分配内存当 Eden 区剩余空间不足以容纳新对象时虚拟机将触发一次 Minor GC。1、两类 GC 的核心差异Minor GCYoung GC仅作用于新生代的垃圾回收动作发生频率高回收速度快。Major GC/Full GC回收范围覆盖老年代、新生代与方法区执行速度通常比 Minor GC 慢 10 倍以上。2、Eden与Survivor区默认8:1:1对象优先在 Eden 区分配空间不足触发 Minor GC。新生代 99% 以上的对象生命周期极短GC 后少量存活对象会在两块 Survivor 区间轮换复制。JVM 默认按 8:1:1 划分 Eden 与两块 Survivor优先扩大 Eden 空间、保证 Survivor 够用即可。默认开启的-XX:UseAdaptiveSizePolicy会动态调整该比例如需固定比例可设置-XX:UseAdaptiveSizePolicy关闭自适应二、 Young GCMinor GC 的完整日志1、举个例子package com.nl; //添加运行JVM参数 -XX:PrintGCDetails public class GCTest { public static void main(String[] args) { byte[] bytes1, bytes2, bytes3; // 60000 KB bytes1 new byte[60000 * 1024]; // 50000 KB bytes2 new byte[50000 * 1024]; // 1000 KB bytes3 new byte[1000 * 1024]; } }2、运行结果[GC (Allocation Failure) [PSYoungGen: 71366K-1354K(132608K)] 71366K-61362K(435712K), 0.0231515 secs] [Times: user0.03 sys0.00, real0.02 secs] Heap PSYoungGen total 132608K, used 55241K [0x000000072c500000, 0x000000073c800000, 0x00000007c0000000) eden space 113664K, 47% used [0x000000072c500000,0x000000072f99fa68,0x0000000733400000) from space 18944K, 7% used [0x0000000733400000,0x00000007335529d0,0x0000000734680000) to space 18944K, 0% used [0x000000073b580000,0x000000073b580000,0x000000073c800000) ParOldGen total 303104K, used 60008K [0x0000000604e00000, 0x0000000617600000, 0x000000072c500000) object space 303104K, 19% used [0x0000000604e00000,0x000000060889a010,0x0000000617600000) Metaspace used 3317K, capacity 4500K, committed 4864K, reserved 1056768K class space used 357K, capacity 388K, committed 512K, reserved 1048576K3、堆内存分布程序执行结束后打印的堆内存分布对应三个数组分配完成后的最终状态。3.1、年轻代 PSYoungGenPSYoungGen total 132608K, used 55241K eden space 113664K, 47% used from space 18944K, 7% used to space 18944K, 0% used年轻代总容量132608K ≈ 129.5MB由1 个 Eden 区 2 个 Survivor 区from/to组成采用复制算法同一时间只有一个 Survivor 处于使用状态。Eden 区113664K ≈ 111MB新对象优先分配的区域。最终占用 47%存放了bytes250000K、bytes31000K以及少量 JVM 运行时对象。From Survivor18944K ≈ 18.5MB上一次 GC 后存活的对象所在地。占用 7%约 1326K和 GC 日志中年轻代回收后的1354K完全对应是 GC 后存活的小对象。To Survivor18944K当前空闲下一次 Young GC 时会用来复制存活对象GC 完成后 from 和 to 会交换身份。3.2、 老年代 ParOldGenParOldGen total 303104K, used 60008K object space 303104K, 19% used老年代总容量303104K ≈ 296MB采用 Parallel Old 收集器标记 - 整理算法。最终占用60008K ≈ 58.6MB正好对应代码里的bytes160000K加上数组对象头的少量开销。这是本次 GC 最关键的现象bytes1作为大对象在 Young GC 时直接晋升到了老年代。3.3、元空间 MetaspaceMetaspace used 3317K, capacity 4500K, committed 4864K, reserved 1056768K class space used 357K, capacity 388K, committed 512K, reserved 1048576K元空间不属于 Java 堆使用本地内存存放类元数据、常量、静态变量等。这里仅占用 3MB 左右属于程序正常的类加载开销和本次对象分配无关。三、面试题1、为什么大对象直接进入老年代大对象指字符串、数组这类需占用大量连续内存的对象。JVM 参数-XX:PretenureSizeThreshold用于定义大对象阈值超出阈值的对象会直接分配到老年代不经过年轻代该参数仅 Serial、ParNew 收集器生效。示例配置-XX:PretenureSizeThreshold1000000单位字节-XX:UseSerialGC运行测试程序可观察到大对象直接进入老年代。设计目的规避大对象内存复制开销提升分配效率2、长期存活的对象将进入老年代JVM 采用分代回收需区分新生代、老年代对象因此为每个对象维护年龄计数器。对象在 Eden 区创建首次 Minor GC 存活且 Survivor 区可容纳时移入 Survivor 并标记年龄为 1后续每熬过一次 Minor GC年龄 1。年龄达到阈值则晋升老年代默认 15 岁CMS 收集器默认 6 岁各收集器阈值略有差异可通过-XX:MaxTenuringThreshold自定义晋升阈值。3、对象动态年龄判断JVM 分代回收依靠对象年龄区分代际。对象诞生于 Eden首次 Minor GC 存活且 Survivor 空间充足则移入 Survivor年龄置 1每躲过一次 Minor GC年龄自增 1。4、什么是老年代空间分配担保机制执行 Minor GC 前JVM 先校验老年代剩余容量若老年代可用空间 新生代全部对象总占用含垃圾则校验参数-XX:-HandlePromotionFailureJDK8 默认开启开启该参数对比老年代剩余空间与历次 Minor GC 晋升对象的平均大小未开启或空间不足直接触发 Full GC整堆回收回收后仍无足够空间则抛出 OOM。若 Minor GC 存活对象晋升所需容量超出老年代剩余空间同样触发 Full GC回收后空间仍不足也会 OOM。四、总结本文结合实战 GC 日志梳理了 JVM 分代回收核心机制Young GC 仅回收新生代高频低耗时Full GC 覆盖全堆耗时长频繁触发多为内存异常信号。新生代采用 Eden 双 Survivor 的复制算法结构对象优先在 Eden 分配空间不足触发 Young GC。对象可通过大对象直入、年龄达标、动态年龄判定三种方式晋升老年代。老年代空间担保为 Young GC 兜底规避晋升失败的 OOM 风险。