如果说收集算法(标记-清除,复制,标记-整理、分代收集算法)是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。目前HotSpot虚拟机所包含的所有收集器如下图。
上面7种作用不同分代的收集器,如果两个收集器之间存在连线,就说明他们可以搭配使用。虚拟机所处的区域,则表示它是属于新生代收集器还是老年代收集器。HotSpot实现如此多的垃圾收集器,就是因为目前没有完美的收集器出现,只能选择对应最合适的收集器。
并行指的是多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
并行指的是用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行在另一个CPU上。
吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+ 垃圾收集时间)。
Serial收集器是最基本、发展历史最悠久的收集器,它是采用复制算法的新生代收集器。顾名思义Serial(串行)收集器是一个单线程收集器。它只会使用一个CPU或者一条收集线程去完成收集工作,在进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。("Stop The World")。曾经在JDK1.3.1之前是新生代收集的唯一选择。
实际上Serial收集器并非是“老而无用,食之无味弃之可惜”的鸡肋,到目前为止,它依然是虚拟机运行在Client模式下默认的新生代收集器。它有着优于其他收集器的地方:简单而高效(与其他收集器的单线程相比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得更高的单线程收集效率。
ParNew收集器是Serial收集器的多线程版本,也是新生代收集器。除了使用多线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数(例如:-XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一样,在是线上,这两种收集器也共用了相当多代码。
ParNew收集器除了多线程收集之外,其他与Serial收集器相比并没有太多创新之处。但它却是许多运行在Server模式下的虚拟机首选的新生代收集器。除了Serial收集器之外,目前只有它能和CMS收集器(Concurrent Mark Sweep)配合工作,CMS收集器是JDK1.5推出的具有划时代意义的收集器。它实现了让垃圾收集线程和用户线程(基本上)同时工作。
ParNew收集器在单CPU环境下绝对不会比Serial收集器更过更好,甚至由于存在线程交互的开销,该收集器在通过超线程技术实现的两个CPU环境都不能百分百超越Serial收集器。在多CPU环境下,它对GC系统资源的有效利用是很有好处的。它默认开启的收集线程与CPU数量相同,在CPU非常多的情况下使用-XX:ParallerGCThreads参数进行设置。
Parallel Scavenge收集器也是一个并行收集器,它也是使用复制算法的收集器。Parallel Scavenge收集器的目标是达到一个可控制的吞吐量(Throughput)。吞吐量高则可以高效地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
Parallel Scavenge收集器提供两个参数用于精准控制吞吐量,分别是最大垃圾停顿时间-XX:MaxGCPauseMillis参数和直接设置屯吞吐量大小的-XX:GCTimeRatio参数。
Parallel Scavenge收集器被称为“吞吐量优先”收集器。Parallel Scavenge收集器还有一个参数-XX:+UseAdaptiveSizePolicy。这是一个开关参数,打开之后就不需要手工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics)。自适应调节机制策略也是Parallel Scavenge收集器与ParNew收集器的一个重要区别。
Parallel Scavenge收集器无法与CMS收集器配合使用,所以在JDK1.6推出Parallel Old之前,如果新生代选择Parallel Scavenge收集器,只能与老年代Serial Old收集器配合使用。
Serial Old是Serial收集器的老年代版本,他同样是单线程收集器,使用标记-整理算法。这个收集器主要意义是给Client模式下的虚拟机使用。如果在Server模式下,有两大用途;
Parallel Old收集器是Parallel Scavenge收集器老年代版本,使用多线程和标记-整理算法。这个收集器在JDK1.6才提供,在此之前新生代的Parallel Scavenge收集器只能选择Serial Old收集器,别无选择(因为Parallel Scavenge收集器无法与CMS收集器配合使用)。所以就诞生了Parallel Old收集器。至此吞吐量优先收集器就有了比较名副其实的应用组合,在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge收集器加上Parallel Old收集器。
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,从Mark Sweep可以看出CMS收集器基于标记-清除算法。它非常符合在互联网或者B/S系统的服务端上的Java应用,这些应用都非常重视服务的响应速度。
它的运作过程分为4个步骤:
总体来说CMS收集器的内存回收过程是与用户线程一起执行的。
CMS收集器的优点:并发收集、低停顿。因此也被称为并发低停顿收集器(Concurrent Low Pause Collector)。
CMS收集器的缺点:
标记-清除算法导致出现大量的空间碎片。空间碎片过多往往会出现老年代还有很大空间,但是无法找到足够大的连续空间分配大对象。不得不提前触发一次Full GC。CMS提供-XX:+UseCMSCompactAtFullCollection开关参数(默认开启),用于CMS收集器顶不住要进行Full GC时开启内存碎片的合并整理,但是过程无法并发,停顿时间不得不变长。
G1(Garbage-First)收集器是当今收集器技术发展最前沿的成果之一。它是一款面向服务端应用的垃圾收集器,HotSpot开发团队赋予它的使命是(在比较长期的)未来可以替换掉JDK1.5中发布的CMS收集器。与其他收集器相比,G1有以下特点。
在G1之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样。G1收集器将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,而都是一部分Region(不需要连续)的集合。
G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(也就是Garbage-First名称的由来)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获得尽可能高的收集效率。
G1把Java堆分为多个Region,就是“化整为零”。但是Region不可能是是孤立的,一个对象分配在某个Region中,它并非只能被本Region中其他对象引用,可以与整个Java堆任意对象发生引用关系。在做可达性判定对象是否存活的时候,需要扫描整个Java堆才能保证准确性。显示是对GC效率的极大伤害。
为了避免全堆扫描,虚拟机为G1中的每个Region维护了一个与之对应的Remember Set。虚拟机发现程序在对Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中,如果是便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remember Set之中。当进行内存回收时,在GC根节点的枚举范围中加入Remember Set即可保证不对全堆扫描也不会有遗漏。
如果不计算维护Remember Set的操作,G1收集器的运作大致可划分为以下几个步骤:
收集器 | 线程 | 串行/并 行/并发 |
新生代/老年代 | 算法 | 目标 | Stop The World |
使用场景 |
Serial | 单线程 | 串行 | 新生代 | 复制算法 | 响应速度优先 | 会 | 单CPU环境下的Client模式 |
ParNew | 多线程 | 并行 | 新生代 | 复制算法 | 响应速度优先 | 不会 | 多CPU环境时在Server模式下与CMS配合使用 |
Parallel Scavenge |
多线程 | 并行 | 新生代 | 复制算法 | 吞吐量优先 | 不会 | 在后台预算不需要太多交互的任务 |
Serial Old | 单线程 | 串行 | 老年代 | 标记-整理算法 | 响应速度优先 | 会 | 单CPU环境下的Client模式、CMS的后备预案 |
Parallel Old | 多线程 | 并行 | 老年代 | 标记-整理算法 | 吞吐量优先 | 不会 | 在后台预算不需要太多交互的任务 |
CMS | 多线程 | 并发 | 老年代 | 标记-清除算法 | 响应速度优先 | 会 | 集中在互联网网站或B/S系统服务端上的Java应用 |
G1 | 多线程 | 并发 | 新生代、老年代 | 分代收集算法 (复制算法+标记-整理算法) |
响应速度优先 | 不会 | 面向服务端应用,将来替换CMS |