JVM垃圾回收算法

JVM垃圾回收算法

介绍

垃圾回收(Garbage Collection,简称GC)主要发生在Java堆和方法区。GC中的垃圾指存在于内存中的不会被使用的对象,回收就是把这些对象进行清理从而释放内存空间。如果垃圾对象一直占用空间则有可能导致内存溢出,可见识别和清理垃圾对象就非常重要了。有了垃圾回收机制,开发人员就只需要关注内存的申请,内存的释放由系统自动识别和完成。

常用的垃圾回收算法包括:引用计数法、标记清除法、标记压缩法、复制算法、分代算法和分区算法。我们分别来看下。

引用计数法(Reference Counting)

引用计数法的实现也比较简单,每个对象的实例都保存着引用的个数,随着被引用的个数增减而增减。但引用计数算法隐含着无法处理循环引用的问题,所以Java虚拟机并未使用该算法作为垃圾回收算法

可达性分析算法.png

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

标记-清除算法是现代垃圾回收算法的思想基础,分为两个阶段:标记阶段清除阶段。标记阶段是通过根节点标记所有可达对象,标记完成后,清除所有的不可达对象。它的主要问题主要有以下两点:

  • 空间问题,标记清除后会产生大量不连续内存碎片,可能导致后续分配大对象没有足够的连续内存而触发另外一次GC。
  • 效率问题,标记和清除的效率都不高,回收的内存比较多时,标记和清除会比较耗时

标记清除法 (1).png

标记-压缩算法(Mark-Compact)

标记压缩算法是一种老年代的回收算法,它在标记清除算法基础上做了一些优化。标记阶段和标记清除算法一样也是从根结点开始对可达对象做标记,压缩阶段则是将标记的存活对象压缩到内存一端,然后清理掉有效对象边界外的所有内存。这种方式相对于标记清除算法避免了大量内存碎片的产生

标记压缩法.png

复制算法(Copying)

复制算法将原有内存按容量分为大小相等的两块,同一时间只有一块内存空间被使用,在进行GC时,将正在使用的内存中的存活对象复制到未使用内存块,之后清除正在使用的内存块中的所有对象,交换两个内存块角色,完成垃圾回收。如下图所示,将内存分为A和B两块:

复制算法工作示意图 (1).png

复制算法实现简单,运行高效,不用考虑内存碎片的问题,但代价也比较高昂,浪费了一半的内存空间。

在Java的新生代串行垃圾回收器中,使用了复制算法的思想,因为新生代的垃圾对象通常会多于存活对象,复制算法的效果会好一些。但不是将新生代内存空间分为两块同样大小的内存空间。

新生代分为eden区、from区和to区3部分(from区和to区也被称为survicor区,即幸存者空间,用于存放未被回收的对象),其中from区和to区可视为用于复制的两块大小相同、地位相等且可角色互换的空间。HotSpot虚拟机默认Eden和Survivor的大小比例为8:1,则只会有10%的内存会被“浪费”,而不是刚开始讲的复制算法的50%。这也算是改进版的复制算法,既保证空间连续性,也避免了大量的空间内存浪费。

在进行垃圾回收,eden区的存活对象和正使用的survivor区(假设是from区)的年轻对象会被复制到未使用的survivor区(假设是to区)(大对象或者老年对象会直接进入老年代,如果to区已满,对象也会直接进入老年代)。然后eden区和from区的垃圾对象可以直接清空。如下图所示显示了新生代改进的复制算法回收过程。

新生代改进复制算法.png

从上图可以看出没有办法保证每次回收都只有不多于10%的对象存活(或者说一个survivor区内存大小对象存活)。当survivor区空间不够的时候,就要依赖老年代分配担保了,其中一些大对象或者老年对象会直接进入老年代,其它对象在survivor满了以后也直接进入老年代。

分代收集算法(Generational Collecting)

分代收集算法就是根据对象存活周期的不同将内存划分为几块,根据每块内存不同特点使用不同的回收算法,来提高GC的效率。一般把Java堆分为新生代和老年代

新生代的特点是每次GC会有大批对象要回收(大约90%的新建对象会被很快回收),只有少量存活,那么就比较适合复制算法,只需付出少量存活对象的复制成功就可以完成收集。老年代特点是对象存活率高,在一段时间内甚至在应用程序的整个生命周期中将是常驻内存的,没有额外空间对其进行额外担保,对老年代的回收使用标记压缩法或者标记清除法

分代回收思想.png

新生代回收频率高,每次回收耗时很短,老年代回收频率比较低,但比较耗时。

分区算法(Region)

分代算法按照对象的生命周期长短分为新生代和老年代,分区算法则是将整个堆空间划分成连续的不同小区间,每个小区间独立使用和回收。通过分区可以控制一次回收小区间的数量。这样根据目标停顿时间,每次可以合理的回收若干小区间,而不是整个堆空间,从而减少GC停顿时间。

分区算法示意图.png

参考:《实战Java虚拟机》

分类:
后端
标签: