Java GC与内存分配紧密协作:对象优先在Eden区分配,经Minor GC存活后移入Survivor区,达年龄阈值或大对象直接进入老年代;GC通过GC Roots可达性分析判定对象存亡,分代收集策略使新生代用复制算法、老年代用标记-清除/整理算法,Eden满触发Minor GC,老年代或元空间不足则触发Full GC。

Java 程序运行时的内存分配与垃圾回收(GC)不是割裂的两个环节,而是紧密协作、动态配合的过程。理解它们如何在对象生命周期中协同工作,是写出稳定、高效 Java 应用的关键。
对象从哪来:内存分配路径清晰可见
新对象的诞生几乎都发生在堆内存中,但具体位置取决于当前堆状态和 JVM 配置: - 大多数对象优先在 Eden 区分配,这是新生代的起始区域; - 若 Eden 空间不足,JVM 会触发一次 Minor GC,回收不可达对象,并将存活对象复制到其中一个 Survivor 区(S0 或 S1); - 当对象在 Survivor 区经历多次 GC 仍存活(默认阈值为 15 次,可通过 `-XX:MaxTenuringThreshold` 调整),就会晋升至老年代; - 大对象(如大数组)可能直接进入老年代,避免在新生代频繁复制,可通过 `-XX:PretenureSizeThreshold` 控制; - 线程本地分配缓冲区(TLAB)被启用时(默认开启),每个线程在 Eden 内划出私有小块,减少多线程分配时的锁竞争。谁决定回收:GC Roots 是判断存亡的唯一依据
GC 不靠引用计数,而是基于可达性分析——从一组可信起点出发,追踪所有可到达的对象: - 虚拟机栈中每个活跃方法的局部变量表里引用的对象; - 方法区中静态字段(static)所引用的对象; - 方法区常量池中字符串常量、类字面量等引用的对象; - 本地方法栈中 JNI(Native 方法)持有的引用; - 正在被 finalizer 队列处理但尚未执行完 `finalize()` 的对象(仅限第一次标记后暂存)。只要对象能通过任意一条引用链连到任一 GC Root,它就被视为“存活”,不会被回收。
GC 怎么配合分配:分代收集让回收更聪明
JVM 将堆按对象年龄划分区域,不同区域采用不同策略,使 GC 更快、更省资源: - 新生代(Eden + 2 Survivor)使用复制算法:只复制存活对象,清理成本低、无碎片,适合短命对象高频创建与消亡; - 老年代使用标记-清除或标记-整理算法:对象寿命长、存活率高,复制开销大,更适合扫描+清理或压缩; - 元空间(替代永久代)不归 GC 管理,但类卸载会触发 Full GC;当元空间不足且未配置自动扩容,也会间接引发 GC; - 分配担保机制存在:Minor GC 前,JVM 会检查老年代是否有足够空间容纳所有可能晋升的对象;若不够,先触发 Full GC,再尝试 Minor GC。什么时候出手:GC 触发不是随机的
GC 行为由明确条件驱动,而非定时轮询: - Eden 区满时,触发 Minor GC(最常见); - 老年代空间不足,或晋升失败(如 Survivor 放不下、老年代没空位),触发 Full GC; - 方法区(元空间)达到 `-XX:MaxMetaspaceSize` 限制,且无法扩展时,也可能触发 Full GC; - 显式调用 `System.gc()` 仅建议 JVM 执行 GC,是否执行由 JVM 决定(HotSpot 默认忽略,除非开启 `-XX:+ExplicitGCInvokesConcurrent`); - CMS 或 ZGC/G1 等并发收集器还支持基于堆使用率、暂停时间目标等预测性触发。不复杂但容易忽略


















