上一篇我们介绍了JVM中几种常见的垃圾收集算法。这一篇介绍下七种经典的垃圾收集器,如下图所示:
上图展示了7种作用于不同分代的垃圾收集器。如果两个收集器之间存在连线,则说明它们可以搭配使用。图中收集器所处的区域,则表示它是属于新生代收集器抑或是老年代收集器。后面会对这7种垃圾收集器做详细介绍,主要是从这些收集器的目标、特性、原理和使用场景进行介绍。
吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即
吞吐量=运行用户代码的时间/(运行用户代码时间+垃圾收集时间)。
假设虚拟机总共运行了100分钟,其中垃圾收集花掉了1分钟,那吞吐量就是99%。
Serial(串行)收集器是JDK1.3之前是虚拟机新生代收集的唯一选择。。它是一个单线程工作的收集器,并且其进行垃圾收集时,用户线程需要暂停,直到垃圾收集结束为止(“Stop The World”)。这就好像你老妈打扫房间时,你需要离开房间一样的道理。其采用的收集算法是标记-复制算法,如下图就是Serial/Serial Old收集器搭配使用的运行示意图。
Serial收集器的优点就是简单高效,额外内存消耗最小。对于单核处理器或者处理器核数较少的环境来说,Serial收集器没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。所以,它依然是HotSpot虚拟机运行在Client模式下的默认的新生代收集器。
ParNew收集器实质上是Serial收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之外,其余的行为包括Serial收集器可用的所有控制参数(例如:-XX:SurvivorRatio)、收集算法、暂停用户线程、对象分配规则、回收策略等都与Serial收集器完全一致。同样的其采用的收集算法是标记-复制算法。下图就是ParNew/Serial Old收集器搭配使用的运行示意图。
ParNew收集器除了使用多线程收集外,其他方面与Serial收集器相比并无太多创新之处,但它却是许多运行在Server模式下的虚拟机中首选的新生代收集器,其中一个重要的原因是,除了Serial收集器之外,目前只有它能和CMS收集器(Concurrent Mark Sweep)配合工作, CMS收集器后面会详细介绍。
ParNew收集器在单CPU的环境中绝对不会有比Serial收集器有更好的效果,在多CPU环境下,随着CPU的数量增加,它对于GC时系统资源的有效利用是很有好处的,它默认开启的收集线程数与CPU的数量相同,可以通过-XX:ParallerGCThreads
参数设置。
Parallel Scavenge收集器也是一款新生代收集器,它同样是基于标记-复制算法实现的收集器,也是能够并行收集的多线程收集器,Parallel Scavenge的诸多特性从表面上看与ParNew非常相似,那它有什么特别之处呢?
Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程停顿的时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。吞吐量的说明前面有提到。
Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是控制最大 垃圾收集停顿时间的 -XX:MaxGCPauseMillis 参数以及直接设置吞吐量大小的 -XX:GCTimeRatio 参数。
-XX:MaxGCPauseMillis 参数允许的值是一个大于0的毫秒数,收集器将尽力保证内存回收花费的时间不超过用户设定值。
-XX:GCTimeRatio参数的值则应当是一个大于0小于100的整数,也就是垃圾收集时间占总时间的比率,相当于吞吐量的倒数。
由于与吞吐量关系密切,Parallel Scavenge收集器也经常被称作"吞吐量优先收集器"。
Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。这个收集器的主要意义也是供客户端模式下的HotSpot虚拟机使用。如果在服务器模式下,它也可能有两种用途, 一种是在JDK1.5以及之前的版本中与Parallel Scavenge收集器搭配使用,另外一种就是作为CMS收集器发生失败时的后备预案。下图就是Serial/Serial Old收集器搭配使用的运行示意图。
Parallel Old收集器是Parallel Scavenage收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实现。在注重吞吐量或者处理器资源较为稀缺的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器这个组合。下图就是Parallel Scavenge/Parallel Old收集器运行示意图。
CMS(Concurrent Mark Sweep)收集器是JDK1.5推出的一个具有划时代意义的收集器,是一种以获取最短回收停顿时间为目标的收集器,它非常符合那些集中在互联网站或者B/S系统的服务端上的Java应用,这些应用都非常重视服务的响应速度。从名字上看它是基于"标记-清除"算法实现的。
CMS收集器工作的这个流程分为4个步骤:
CMS是一款优秀的收集器,它最主要的优点就是:并发收集、低停顿,因此CMS收集器也被称为并发低停顿收集器(Concurrent Low Pause Collector)。
G1(Garbage-First)收集器是当今收集器技术发展最前沿的成果之一,他是一款面向服务端应用的垃圾收集器,HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器。与其他GC收集器相比,G1具备如下特点:
在G1之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样的,G1在使用时,Java堆的内存布局与其他收集器有很大区别,它将整个Java堆划分为多个大小相等的独立区域(Region), 虽然还保留新生代和老年代的概念,但新生代和老年代不再是物理隔离的,而都是一部分Region(不需要连续)的集合。
G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需要时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(这也就是Garbage-First名称的来由)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。
G1把Java堆分为多个Region,就是"化整为零"。但是Region不可能是孤立的,一个对象分配在某个Region中,可以与整个Java堆任意的对象发生引用关系。在做可达性分析缺点对象是否存活的时候,需要扫描整个Java堆才能保证准确性,这显然是对GC效率的极大伤害。
为了避免全堆扫描的发生。虚拟机为G1中每个Region维护了一个与之对应的RememberedSet。虚拟机发现程序在对Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中(在分代的例子中就是检查是否老年代中的对象引用了新生代中的对象),如果是,便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set之中。当进行内存回收时,在GC根节点的枚举范围内加入Remembered Set即可保证不对全堆扫描也不会有遗漏。
如果不计算维护Remembered Set的操作,G1收集器的运作大致可划分为以下几个步骤 :
本文主要介绍了JVM七种经典的收集器做完了详细的说明。之所以要有这么多收集器,是因为没有一种收集器是完美的。在JDK1.5 推出来具有划时代意义的CMS收集器,它的特点就是并发收集,低停顿。而JDK1.7推出了收集器的集大成者—G1收集器。它的特点就是并发收集,可预测的停顿,不会产生碎片化。
收集器 | 串行、并行or并发 | 新生代/老年代 | 算法 | 目标 | 适用场景 |
---|---|---|---|---|---|
Serial | 串行 | 新生代 | 标记-复制算法 | 响应速度优先 | 单CPU环境下Client模式 |
ParNew | 并行 | 新生代 | 标记-复制算法 | 响应速度优先 | 多CPU环境时在Server模式下与CMS配合 |
Parallel Scavenge | 并行 | 新生代 | 标记-复制算法 | 吞吐量优先 | 在后台运算不需要太多交互的任务 |
Serial Old | 串行 | 老年代 | 标记-整理算法 | 响应速度优先 | 单CPU环境下的Client模式、CMS的后备预案 |
Parallel Old | 并行 | 老年代 | 标记-整理算法 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 |
CMS | 并行 | 老年代 | 标记-清除 | 响应速度优先 | 集中在互联网站或B/S系统服务端上的Java应用 |
G1 | 并行 | 所有 | 标记-整理+标记-复制算法 | 响应速度优先 | 面向服务端应用,将来替换CMS |
深入理解JVM(3)——7种垃圾收集器
深入理解Java虚拟机(第3版)