这篇文章主要接着上篇博客针对jvm是如何进行垃圾回收的进行介绍。下面将分别介绍主要的回收算法和所用到的垃圾收集器,帮忙大家对jvm垃圾回收机制有更全面的认识。
1)垃圾收集算法
垃圾收集算法,主要包括四种分别是:”标记-清除”(Mark-Sweep)、“复制”(Copying)、“标记-整理”(Mark-Compact)、“分代收集”(Generational Collection)算法。
① 标记-清除 算法
如同它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。如下图:
② 复制算法
它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。如下图:
③ 标记-整理算法
该算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,“标记-整理”算法的示意图如图所示。
④ 分代收集算法
当前商业虚拟机的垃圾收集都采用“分代收集”(Generational Collection)算法,这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收.
算法名称 | 优势 | 劣势 |
标记-清除 | 实现简单 | 1、标记清除效率低 2、内存空间不连续,不能放大对象 |
复制 | 1、效率高(只要移动堆顶指针,按顺序称移动就行) 2、有连续的内存块 | 1、浪费一半内存 |
标记-整理 | 有连续内存 | 效率不高 |
分代收集 | 实现复杂 | 分情况用不同的算法,效率高 |
2)垃圾收集器
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。HotSpot虚拟机包含的所有收集器如图所示:

②ParNew收集器
ParNew收集器其实就是Serial收集器的多线程版本,收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一样,在实现上,这两种收集器也共用了相当多的代码。ParNew收集器的工作过程如下图所示。
③Parallel Scavenge收集器
Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器。Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,其他收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。(吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间))
④ Serial Old收集器
Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整理”算法。
⑤Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。
⑥ CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,CMS收集器是基于“标记—清除”算法实现的,它的运作过程相对于前面几种收集器来说更复杂一些,整个过程分为4个步骤,包括:
初始标记(CMS initial mark)
并发标记(CMS concurrent mark)
重新标记(CMS remark)
并发清除(CMS concurrent sweep)
其中,初始标记、重新标记这两个步骤仍然需要“Stop The World”。用时最长并发标记、并发清除阶段是可以和用户线程并发运行的
⑦ G1收集器
G1(Garbage-First)收集器是当今收集器技术发展的最前沿成果之一。G1从整体来看是基于“标记—整理”算法实
现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的。G1收集器的运作大致可划分为以下几个步骤:
并发标记(Concurrent Marking)
最终标记(Final Marking)
筛选回收(Live Data Counting and Evacuation)
这里主要要对G1收集器做一个更具体的说明。在G1之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。
G1会跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(这也就是Garbage-First名称的来由)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。
还有一个问题,一个对象分配在某个Region中,它并非只能被本Region中的其他对象引用,而是可以与整个Java堆任意的对象发生引用关系。那在做可达性判定确定对象是否存活的时候,如何才能保证准确性?
在G1收集器中,Region之间的对象
引用以及其他收集器中的新生代与老年代之间的对象引用,虚拟机都是使用Remembered Set来避免全堆扫描的。G1中每个Region都有一个与之对应的Remembered Set,虚拟机发现程序在对Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中(在分代的例子中就是检查是否老年代中的对象引用了新生代中的对象),如果是,便通过被引用对象会被记录到所属的Region的Remembered Set之中。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏。
总结
各收集器优缺点
名称 | 优点 | 缺点 |
Serial 收集器 | 简单而高效(与其他收集器的单线程比),对于限定单个 CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最 高的单线程收集效率 | 单线程的收集器,在进行收集垃圾时,必须stop the world |
Serial Old收集器 | Serial收集器的老年代版本 | 单线程的收集器,在进行收集垃圾时,必须stop the world |
ParNew收集器 | Serial收集器的多线程版本,除Serial外,只有它能与CMS收集器配合工作 | 在进行收集垃圾时,必须stop the world |
Parallel Scavenge收集器 | 目标是达到一个可控制的吞吐量(高吞吐量则可以高效率地利用CPU时间,主要适合在后台运算而不需要太多交互的任务。) | 在进行收集垃圾时,必须stop the world |
Parallel Old收集器 | Parallel Scavenge收集器的老年代版本 | 在进行收集垃圾时,必须stop the world |
CMS收集器 | 并发收集、低停顿 | CPU资源非常敏感、无法处理浮动垃圾、收集结束会产生大量空间碎片 |
G1收集器 | 并行与并发、分代收集、空间整合、可预测的停顿 | 成熟版本的发布时间短,还没有经过实际应用的考验,性能测试贫乏 |