Java通过可达性分析算法判断对象是否可回收:从GC Roots出发,无引用链可达的对象才可能被回收,需经两次标记后才能进入回收队列。

怎么判断一个对象该被回收了?不是看引用计数
Java不用引用计数算法,哪怕它实现简单、效率高——因为循环引用会导致两个对象互相“挽留”,计数永远不为0,内存就漏了。实际用的是可达性分析算法:从GC Roots(比如栈帧里的局部变量、静态字段、常量池项、JNI引用)出发,顺着引用链往下找;凡是没有路径连到GC Roots的对象,才可能被回收。注意,“可能”不等于“立刻回收”——还要经历两次标记(比如finalize()方法是否执行过),才能真正进回收队列。
标记-清除、复制、标记-整理,三者怎么选?看内存区域
不同算法不是凭空选的,而是和堆结构强绑定:
-
标记-清除:只标记+清空,不移动对象,所以快但碎片多。老年代对象存活率高,移动成本大,适合用它,但得配合内存整理策略或触发Full GC来缓解碎片 -
复制算法:Eden区 + 两个Survivor区(S0/S1)就是典型实现。每次把Eden和其中一个Survivor里活的对象拷到另一个Survivor,然后整块清空。98%对象朝生夕死,所以拷贝少、无碎片;但代价是总有一半Survivor空间闲置(默认比例是Eden:S0:S1 = 8:1:1) -
标记-整理:老年代常用。标记完把所有活对象往内存一端压,再清理边界外内存。不碎片、不浪费空间,但要移动对象+更新所有引用指针,STW时间比标记-清除更长
为什么分代回收是默认方案?不是因为它高级,而是因为它真实
分代不是玄学,是基于大量应用观测得出的经验规律:绝大多数对象生命周期极短(比如方法内临时创建的StringBuilder、ArrayList),只有极少数能活过几次Minor GC。所以JVM把堆切成年轻代(Eden + S0 + S1)和老年代,分别施以不同策略:
- 年轻代用
复制算法:轻量、快停顿、适配高死亡率 - 老年代用
标记-整理或标记-清除:避免频繁拷贝长寿命对象 - 对象年龄达阈值(默认15次Minor GC)自动晋升到老年代;大对象(如大数组)可能直接分配进老年代,跳过年轻代
这个设计让GC停顿时间可预测,也大幅降低整体回收频率。
立即学习“Java免费学习笔记(深入)”;
别光看算法名字,得盯住实际触发条件和副作用
算法本身没好坏,关键是你调参后它在什么场景下“爆发”:
- 频繁
Minor GC?可能是Eden太小,或对象创建太快(比如循环里new HashMap) - 老年代突然涨满触发
Full GC?常见原因是年轻代对象“躲过”晋升阈值,或有大对象直接进老年代,又或者元空间(Metaspace)爆了 - 用
Parallel GC时吞吐优先,但单次停顿可能很长;用G1 GC则追求可控停顿,但需要合理设置MaxGCPauseMillis,否则反而更慢
最常被忽略的一点:GC算法效果高度依赖对象分配模式和引用生命周期。写代码时随手new一堆短命对象没问题,但若长期持有static Map又忘了清理,再好的算法也救不了内存泄漏。








