Jvm中gc的算法演进
垃圾回收是JVM的一大功能模块,面试中经常会被问到有哪些垃圾回收算法,那么这些垃圾回收算法是如何被设计出来的呢?首先要回到根本的问题上来。
问题的起源:为何需要垃圾回收
在早期编程语言中,程序员需要手动管理内存:分配内存并在不需要时释放。这一过程容易出错,导致内存泄漏(未被释放的内存)或悬挂指针(指向已释放内存的指针),严重影响程序稳定性和性能。为解决这些问题,垃圾回收机制应运而生,目的是自动检测并回收不再被程序使用的内存。
1. 标记-清除算法(Mark-Sweep)
JVM堆内存用于存储Java对象,对于堆内存如何管理就需要有些考究了,首先有一块空白内存等待被使用。当新对象需要被分配空间的话,我们可以采用简单直接的遍历方式按需分配即可,等到分配的对象不需要时,则需要清理对应空间,后面再创建新对象时可以使用已回收空间,这就是我们直接想到的标记-清除算法:

这种方法简单直接,但它的问题是内存碎片化,零碎的内存多了,当一个新的大的对象要创建时,会遇到连续的空间不够的情况,这就需要对已经存活的对象进行迁移地址,这影响到了内存分配效率。
2. 复制算法(Copying)
为解决内存碎片问题,复制算法被引入。它将内存分为两个相等的区域,每次只使用其中一个。垃圾回收时,算法会将活动对象从当前区域复制到另一个区域,然后清除当前区域的所有对象,从而避免碎片化。

赋值算法依然有其局限性:
内存利用率:复制算法需要将内存分成两个区域,一次只使用其中一个区域来存放对象。这意味着在任何时刻,有一半的内存空间不被利用,从而降低了内存的有效利用率。
对象复制开销:虽然复制存活对象到另一区域可以避免碎片化,但这个过程需要复制对象,并更新引用这些对象的所有指针。对于存活对象较多的场景,这种复制动作可能会带来不小的性能开销。
这就导致在遇到大对象(涉及到对象不断复制复制)、对象多的情况下,也会有性能问题。
3. 分代收集算法
分代收集算法基于这样一个观察:不同对象的生命周期不同。因此,JVM的内存被划分为年轻代(Young Generation)和老年代(Old Generation),以不同的方式管理不同寿命的对象。
年轻代:新创建的对象首先被分配到年轻代。年轻代进一步细分为Eden区、From Survivor区和To Survivor区。大部分对象在这里被快速回收。
老年代:经过多次垃圾回收依然存活的对象,会被移动到老年代。老年代的对象生命周期较长,垃圾回收频率较低。
4. Eden区、From、To区
Eden区:新生成的对象首先被分配到这里。
From和To Survivor区:用于实现复制算法的一部分。在每次年轻代垃圾回收时,存活的对象会从Eden区和一个Survivor区(例如From)复制到另一个Survivor区(例如To),然后清空Eden区和之前的From Survivor区。
5. 标记-整理算法(Mark-Compact)
用于老年代的标记-整理算法,它在标记阶段后不是简单地清除未标记对象,而是将所有存活的对象移动到内存的一端,然后清理边界以外的内存,从而解决内存碎片问题,适用于老年代中对象生命周期较长的特点。
结论
JVM的垃圾回收机制通过引入复制算法、分代收集算法、Eden区、From/To区的概念,以及标记-整理算法等技术,有效地解决了内存管理中的关键问题,如自动内存回收、内存碎片化,以及不同生命周期对象的高效管理。这些技术的引入,是基于对程序内存使用模式的深入理解和长期观察的结果,目的是优化内存回收过程,提高程序性能和稳定性。