引言
此篇是接着上一篇《垃圾回收算法》,也算是GC的下篇。
上一篇将 如何判定对象为垃圾的两种算法 和 垃圾回收的四种算法整理完毕了。由于适用的场景不同,垃圾收集器也是多种多样,这一篇准备介绍常用的几种垃圾回收器。
Serial 收集器
1. 首先,Serial收集器是最基本的、发展最悠久的垃圾收集器。
2. 它还是一个单线程垃圾收集器。假如有很多个线程正在运行,当需要垃圾回收的时候,所有线程全部暂停,然后垃圾回收线程开始执行。垃圾回收器执行完毕后,所有线程恢复,继续执行。
明明可以多线程执行,为什么垃圾收集器不与其他线程同时进行呢?
因为其他线程不停制造垃圾,垃圾回收器边清除垃圾,那垃圾将永远都打扫不完。就像妈妈在扫地,你在这边扔垃圾?
用途:在客户端(桌面应用)。因为客户端JVM所分配的内存非常小;又因为它是单线程的执行效率非常高,所以它收集垃圾的时间非常短,可以说基本上是无感知的。
ParNew 收集器
1. ParNew收集器是多个垃圾收集器同时进行的,性能得到提升,但在客户端还是不如Serial。
2. ParNew收集器与Serial收集器功能大部分都是相同的;且实现原理都是复制算法。
3. 在新生代内存必须使用Serial和ParNew。
Parallel Scavenge 收集器
1. 采用复制算法,主要回收新生代,所以为新生代收集器。
2. 多线程收集器。
3. 达到可控制的吞吐量(CPU用于运行代码的时间 与 CPU消耗的总时间的比值)。
吞吐量 = 执行用户代码时间 / (执行用户代码时间 + 垃圾回收时占用的时间)
那如何控制所说的吞吐量呢?这有两个参数可以控制。
-XX:MaxGCPauseMillis //控制最大垃圾收集停顿时间
-XX:GCTimeRatio //直接设置吞吐量大小(值的范围在(0,100))
既然最大的停顿时间可以控制,是不是最大停顿时间小了,性能就可以有提高呢?
不会的。该垃圾收集器是收集新生代内存的,垃圾回收器再进行垃圾区域划分的时候,停顿时间短的要比停顿时间长的新生代区域要小。
因为只有新生代区域变小了,垃圾收集器才能在规定的时间内完成回收。
并且,停顿时间短了,所带来的是垃圾收集变高。
Concurrent Mark Sweep (CMS) 收集器
1. 采用标记清除算法,所以它属于老年代收集器。
2. 属于并发收集器,但并不是完全的并发。
并发:回收垃圾和制造垃圾同时进行。
并行:回收垃圾和制造垃圾是分开的,但有多个同时进行。
了解了并发和并行,下面就介绍他的工作流程了。
它的主要工作流程包括:
1. 初始标记。标记GCRoot能直接关联到的对象。
2. 并发标记。顺着GCRoot能直接关联到的对象继续向下找。
3. 重新标记。对并发标记进行修正。
4. 并发清理。将标记的对象进行清除。
优点:并发收集;低停顿。
缺点:占用CPU资源高(因为并行执行);
无法处理浮动垃圾(刚清理完垃圾,立马又制造出垃圾,此种垃圾无法处理);
出现Concurrent Mode Failure(由于用户线程在创建对象,且标记或者清理仍在执行,所以它专门留出了一块区域,当有新的对象被创建,就往这块区域去存放。如果这块区域过大,就会浪费空间;如果过小,就会出现Concurrent Mode Failure错误。如果出现了这个错误就会触发一个Serial收集器来进行单线程回收,这个过程更加耗时);
有大量空间碎片(标记清除算法所导致)。
G1 收集器
内存并不是清晰的划分出新生代和老年代了,即使有,也是逻辑上的,不像以前直接物理上的隔离了。
G1收集器可算是之前所有收集器的优势整合,具体的优势有:
1. 并行:利用多核优势,缩短了停顿的时间;并发:提高速度。
2. 分代收集。将内存分成一块一块的Region,在后台存一张表(Remembered Set)来记录所有Region的回收情况。
3. 空间整合。采用类似于 标记-整理 算法,在进行大的对象分配的时候,就能找到大的内存,避免多次垃圾回收。
4. 可预测的停顿。可以让使用使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不超过N毫秒。
G1收集器的运作步骤的前三点与CMS收集器非常相似:
1. 初始标记;
2. 并发标记(并发);
3. 最终标记(并发);
4. 筛选回收(并发),假如想增加吞吐量,可以选择筛选回收这一过程为并行,并行执行可以提高垃圾回收效率。
Region之间对象引用以及其他收集器中的新生代和老年代之间的对象引用,如何避免全堆扫描?
使用Remembered Set避免全堆扫描。G1每个Region都有一个与之相对应的Remembered Set,虚拟机发现程序在进行对Reference类型的数据进行写操作的时候,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中。
如果是,便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set之中。当进行内存回收时,在GC根结点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏。