zl程序教程

您现在的位置是:首页 >  Java

当前栏目

看一看JVM垃圾回收算法

2023-02-18 16:46:03 时间

Java有着自己一套的内存管理机制,不需要开发者去手动释放内存,开发者只需要写好代码即可,运行过程中产生的垃圾都由JVM回收。那JVM都是用哪些算法进行垃圾回收呢?

标记-清除(Mark-Sweep)算法

标记-清除(Mark-Sweep)算法是最早出现也是最基础的垃圾收集算法。顾名思义,标记-清除算法分为两个阶段:

  • 标记:标记出所有需要回收的对象
  • 清除:清除掉所有被标记的对象

标记-清除前后

「优点:」

  • 简单,容易理解
  • 垃圾少时,效率高

「缺点:」

  • 会产生内存空间碎片,内存空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾回收动作
  • 性能不稳定,如果内存中大部分都是要回收的垃圾对象,标记和清除效率会随着垃圾的数量而降低

复制(Copying)算法

为了解决标记-清除算法面对大量可回收对象时执行效率低的问题,于是有了复制(Copying)算法。复制算法是将内存一分为二,当一块内存使用完成之后,会将存活的对象移动到另一块预留未使用的内存空间,然后将使用过的那块内存空间清理掉。

复制前后

「优点:」

  • 实现简单,运行高效
  • 不会产生内存空间碎片

「缺点:」

  • 因为要预留一半内存空间,所以内存空间浪费严重
  • 如果内存中多数对象都是存活的,这种算法将会产生大量的内存间复制的开销

Appel 式回收

在1989年,Andrew Appel针对具备“朝生夕灭”特点的对象,提出了一种更优化的半区复制分代策略,现在称为“Appel式回收”。具体做法是分配一块较大的Eden区和两块较小的Survivor区(可以叫做From区和To区,也可以叫做Survivor1区和Survivor2区)。

Img

研究表明,新生代中的对象98%是“朝生夕死”的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的「Eden」空间和两块较小的「Survivor」空间,每次使用Eden和其中一块 Survivor空间。当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和已经用过的Survivor空间。也就是说,浪费的空间只有一个Survivor空间。

HotSpot虚拟机新生代比例Eden和Survivor空间比例为8:1:1,在使用的时候只使用Eden区和一个Survivor区,也就是说,新生代有90%的空间是在使用的,只有10%的空间是浪费的。新生代中98%的对象可被回收仅仅是“普通场景”下测得的数据,任何人都没有办法百分百保证每次回收都只有不多于10%的对象存活,因此,在Survivor空间不足的情况下,就需要依赖其他内存进行分配担保(Handle Promotion),一般是老年代。就是说如果Survivor空间不足,对象就需要以分配担保的形式进入老年代。

标记-整理(Mark-Compact)算法

标记-整理算法首先标记出所有需要回收的对象,在标记完成后,后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

标记-整理前后

「优点:」

  • 不会产生内存空间碎片
  • 相较于复制算法,不会有多余空间浪费

「缺点:」

  • 需要移动对象,效率低。对象移动不单单会加重系统负担,同时需要全程暂停用户线程才能进行,也就是常说的“Stop The World”,同时所有引用对象的地方都需要更新。

标记整理与标记清除算法的区别主要也在于对象的移动。

总结

垃圾回收算法没有好坏之分,只是应用场景不同。老年代存活对象比较多,所以一般采用标记-清除和标记-整理算法,相反,新生代存活对象比较少,所以一般采用复制算法。根据不同的区域的特征采用不同的算法,这就是分代收集理论。