为了达到最大性能,基于分代管理和回收算法,结合回收的时机,JVM实现垃圾回收器了:串行回收、并行回收、并发标记回收(CMS)和垃圾优先回收。

1.串行回收

使用单线程进行垃圾回收,在回收的时候Mutator需要STW。新生代通常采用复制算法,老生代通常采用标记压缩算法。

复制算法:

复制算法的实现也有很多种,可以使用两个分区,也可以使用多个分区。使用两个分区时内存的利用率只有50%;使用多个分区(如3个分区),则可以提高内存的使用率。把堆空间分为1个新生代(分为3个分区:Eden、Survivor0、Survivor1)、1个老生代的收集过程如下:

1.普通对象创建的时候都是放在Eden区,S0和S1分别是两个存活区。第一次垃圾收集前S0和S1都为空,在垃圾收集后,Eden和S0里面的活跃对象(即可以通过根集合到达的对象)都放入了S1区

2.回收后Mutator继续运行并产生垃圾,在第二次运行前Eden和S1都有活跃对象,在垃圾收集后,Eden和S1里面的活跃对象(即可以通过根节点到达的对象)都被放入到S0区,一直这样循环收集

标记压缩算法(标记整理算法):

在理解标记压缩算法前,要先明白标记清除算法,因为标记压缩算法是为了解决标记清除算法中使内存碎片化的问题,除了标记清除的标记动作之外,还会把活跃对象重新整理从头开始排列,减少内存碎片。标记清除:从根集合出发,遍历对象,把活跃对象入栈,并依次处理。处理方式可以是广度优先搜索也可以是深度优先搜索(通常使用深度优先搜索,节约内存)。标记出活跃对象之后,就可以把不活跃对象清除。

 

2.并行回收

并行回收使用多线程进行垃圾回收,在回收的时候Mutator需要暂停,新生代通常采用复制算法,老生代通常采用标记压缩算法。

 

3.并发标记回收

这个算法通常适用于老生代,新生代可以采用并行回收。

 

(1)初始标记

这是CMS中两次stop-the-world事件中的一次。它有两个目标:一是标记老年代中所有的GC Roots;二是标记在老年代中被年轻代中活着的对象引用的对象。

 

 

 (2)并发标记

这个阶段会遍历整个老年代并且标记所有存活的对象,从“初始化标记”阶段标记的结点开始。并发标记的特点是和应用程序线程同时运行。并不是老年代的所有存活对象都会被标记,因为标记的同时应用程序会改变一些对象的引用等。

 

 (3)并发预清理

这个阶段又是一个并发阶段,和应用线程并行运行,不会中断他们。前一个阶段在并行运行的时候,一些对象的引用已经发生了变化,当这些引用发生变化的时候,JVM会标记堆的这个区域为Dirty Card(包含被标记但是改变了的对象,被认为"dirty"),这就是 Card Marking。

 

 在该阶段会做预清理动作,那些能够从dirty card对象到达的对象也会被标记,这个标记做完之后,dirty card标记就会被清除了。

另外,一些必要的清扫工作也会做,还会做一些final remark阶段需要的准备工作;

 

 (4)并发中断预清理

又一个并发阶段不会停止应用程序线程。这个阶段尝试着去承担STW的Final Remark阶段足够多的工作。这个阶段持续的时间依赖好多的因素,由于这个阶段是重复的做相同的事情直到发生aboart的条件(比如:重复的次数、多少量的工作、持续的时间等等)之一才会停止。

 

(5)最终标记

这个阶段是CMS中第二个并且是最后一个STW的阶段。该阶段的任务是完成标记整个年老代的所有的存活对象。由于之前的预处理是并发的,它可能跟不上应用程序改变的速度,这个时候,STW是非常需要的来完成这个严酷考验的阶段。
通常CMS尽量运行Final Remark阶段在年轻代是足够干净的时候,目的是消除紧接着的连续的几个STW阶段。

 

(6)并发清理

和应用线程同时进行,不需要STW。这个阶段的目的就是移除那些不用的对象,回收他们占用的空间并且为将来使用。

 

 (7)并发重置

 

这个阶段并发执行,重新设置CMS算法内部的数据结构,准备下一个CMS生命周期的使用。

 

 

 

4.垃圾优先回收垃圾优先回收器(Garbage-First,也称为G1)

G1致力于在多CPU和大内存服务器上对垃圾回收提供软实时目标和高吞吐量。G1垃圾回收器的设计和前面提到的3种回收器都不一样,它在并行、串行以及CMS GC针对堆空间的管理方式上都是连续的,连续的内存将导致垃圾回收时收集时间过长,停顿时间不可控。因此G1将堆拆成一系列的分区(Heap Region),这样在一个时间段内,大部分的垃圾收集操作只针对一部分分区,而不是整个堆或整个(老生)代,在G1里,新生代就是一系列的内存分区,这意味着不用再要求新生代是一个连续的内存块。类似地,老生代也是由一系列的分区组成。这样也就不需要在JVM运行时考虑哪些分区是老生代,哪些是新生代。事实上,G1通常的运行状态是:映射G1分区的虚拟内存随着时间的推移在不同的代之间切换。例如一个G1分区最初被指定为新生代,经过一次新生代的回收之后,会将整个新生代分区都划入未使用的分区中,那它可以作为新生代分区使用,也可以作为老生代分区使用。很可能在完成一个新生代收集之后,一个新生代的分区在未来的某个时刻可用于老生代分区。同样,在一个老生代分区完成收集之后,它就成为了可用分区,在未来某个时候可作为一个新生代分区来使用。

G1新生代的收集方式是并行收集,采用复制算法。与其他JVM垃圾回收器一样,一旦发生一次新生代回收,整个新生代都会被回收,这也就是我们常说的新生代回收(Young GC)。但是G1和其他垃圾回收器不同的地方在于:

(1)G1会根据预测时间动态改变新生代的大小

其他垃圾回收新生代的大小也可以动态变化,但这个变化主要是根据内存的使用情况进行的。G1中则是以预测时间为导向,根据内存的使用情况调整新生代分区的数目。

(2)G1老生代的垃圾回收方式与其他JVM垃圾回收器对老生代处理有着极大的不同。

G1老生代的收集不会为了释放老生代的空间对整个老生代做回收。相反,在任意时刻只有一部分老生代分区会被回收,并且,这部分老生代分区将在下一次增量回收时与所有的新生代分区一起被收集。这就是我们所说的混合回收(Mixed GC)。在选择老生代分区的时候,优先考虑垃圾多的分区,这也正是垃圾优先这个名字的由来

(3)从实现角度来看,G1算法是复合算法,吸收了以下算法的优势:列车算法,对内存进行分区,·CMS,对分区进行并发标记。·最老优先,最老的数据(通常也是垃圾)优先收集。

 

posted on 2020-05-23 00:35  Moonshoterr  阅读(189)  评论(0编辑  收藏  举报