在JVM内存管理系列博文中,我们已经了解了内存区域划分、对象创建和内存分配机制。本文将深入探讨垃圾回收(Garbage Collection, GC)的标记算法和回收算法,这两个概念是垃圾回收的核心。
1 垃圾标记算法
垃圾回收的第一步是识别出内存中哪些对象是“垃圾”,即不再被任何引用所指向的对象。标记算法负责这一过程:
1.1 引用计数法
- 基本思想:给对象添加一引用计数器,被引用一次计数器值就加 1;当引用失效时,计数器值就减 1;计数器为 0 时,对象就是不被使用的。
- 优点:简单,实时性高。
- 缺点:无法处理循环引用,且计数器的更新可能会影响性能。如下面代码所示:objA 和 objB 互相引用对方,导致它们的引用计数器都不为0,于是引用计数算法无法通知 GC 回收器回收他们。
public class ReferenceCountingGc {
Object instance = null;
public static void main(String[] args) {
ReferenceCountingGc objA = new ReferenceCountingGc();
ReferenceCountingGc objB = new ReferenceCountingGc();
// 出现了循环引用
objA.instance = objB;
objB.instance = objA;
// 此时依然无法回收
objA = null;
objB = null;
}
}
1.2 根可达算法
- 基本思想:这种实现方案通过一系列的称为 "GC Roots" 的对象作为起始点,扫描整个引用链,找到所有可达的对象都标记为非垃圾对象。
- 优点:可以处理循环引用,JVM使用的就是此算法。
- 缺点:需要停顿整个应用程序(Stop-The-World, STW)以确保标记过程中对象图的一致性。
1.2.1 GC Roots对象
- 虚拟机栈栈帧中本地变量表引用的对象
- 方法区中类静态属性引用
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象
1.2.2 引用类型
java的引用类型一般分为四种:强引用、软引用、弱引用、虚引用
- 强引用:普通的变量引用
public static User user = new User();
- 软引用:将对象用SoftReference软引用类型的对象包裹,正常情况不会被回收,但是GC做完后发现释放不出空间存放新的对象,则会把这些软引用的对象回收掉。软引用可用来实现内存敏感的高速缓存。
public static SoftReference<User> user = new SoftReference<User>(new User());
- 弱引用:将对象用WeakReference软引用类型的对象包裹,弱引用跟没引用差不多,GC会直接回收掉,很少用
public static WeakReference<User> user = new WeakReference<User>(new User());
- 虚引用:虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系,几乎不用
2 垃圾回收算法
一旦标记算法完成了对象的标记,回收算法就会负责采用合适的方式回收未被标记的对象所占用的内存:
2.1 标记-清除算法
- 基本思想:先标记所有活跃对象,然后清除未被标记的对象。
- 优点:实现简单。
- 缺点:
- 效率问题,清除过程需要遍历内存区块,效率不高;
- 空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致在需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
2.2 复制算法
- 基本思想:为了解决效率问题,复制算法将内存划分为大小相等的两块,每次使用一块,这一块满后,将尚存活的对象复制到另一块上去,把当前块清空。
- 优点:不存在内存碎片,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
- 缺点:可用内存被压缩为原来的一半,且存活对象多的话,此算法效率大大降低。
- 结论:适合存活对象少,需要频繁垃圾回收的内存。
2.3 标记-整理算法
- 基本思想:在标记-清除法的基础上,将所有存活的对象整理到内存的一端,然后清理边界外的内存。
- 优点:解决了内存碎片问题。
- 缺点:效率低,需要移动对象,可能导致额外的开销。
- 结论:适合存活对象多,且不经常垃圾回收的区域,可以和标记-清理算法结合使用
2.4 分代收集算法
- 基本思想:前面三种收集算法的结合,根据对象的存活时间将堆内存分为不同的区域,并分别采用不同的垃圾收集算法。
- 优点:利用对象的生命周期特性,提高垃圾回收的效率。
- 缺点:需要更复杂的管理逻辑。
JVM的垃圾收集普遍都采用”分代收集”算法,根据对象存活周期的不同将堆内存划分为分为新生代和老年代
- 在新生代中,每次垃圾收集时都会有大批对象死去,只有少量存活,因此采用复制算法只需要付出少量存活对象的复制成本就可以完成收集。
- 在老年代中,每次垃圾收集时对象存活率高,使用“标记—清理”或者“标记—整理”算法来进行回收。(如果内存碎片太多,可以先进行整理)
通常发生新生代的的垃圾收集动作被叫做Minor GC,发生老年代的的垃圾收集动作被叫做Full GC。
- Minor GC/Young GC非常频繁,回收速度一般也比较快。
- Major GC/Full GC一般会回收老年代 ,年轻代,方法区的垃圾,Major GC的速度一般会比Minor GC的慢10倍以上。
3 结语
在本篇博文中,我们探讨了JVM垃圾回收机制的核心内容,包括垃圾标记算法和垃圾回收算法。
然而该领域其实还有更多值得探索的话题。例如,三色标记算法是提高垃圾收集器效率的关键技术,它通过颜色标记区分对象状态,有助于减少Stop-The-World(STW)时间。此外,finalize()
方法提供了一种在对象被回收前执行清理操作的机制,虽然实际应用有限,但也是一个有价值的知识点。读者可以根据自己的兴趣和需求进行扩展学习,当然我也可能会在将来的博文中另行补充这些话题,以提供更详尽的解释和示例。
在下一篇章中,我们将着重讨论JVM中各种主要的垃圾收集器,包括Serial、Parallel、CMS、G1等,以及它们的特点、适用场景和调优技巧。这些内容将帮助读者更全面地了解JVM的垃圾回收器,并为实际应用中的性能优化提供指导。