上篇博客介绍了垃圾回收算法,如果说垃圾回收算法是内存回收的方法论,那么垃圾回收器就是内存回收的具体实现。
上图是基于JDK1.7 Update 14之后的HotSpot虚拟机中包含的垃圾收集器,总共有7种收集器。如果两个收集器之间存在连线,就说明它们可以搭配使用;而垃圾回收器所处的区域,则表示它是属于新生代收集器还是老年代收集器。 本文逐一介绍这些收集器的特性、基本原理和使用场景。
这两个名词都是并发编程中的概念,在谈论垃圾收集器的上下文语境中,它们可以解释如下。
吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)。 虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
Serial收集器是最基本、发展历史最悠久的收集器,曾经(在JDK 1.3.1之前)是虚拟机新生代收集的唯一选择。
Serial / Serial Old 收集器运行示意图
这个收集器是一个单线程
的收集器,但它的“单线程”的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。Stop The World
Serial收集器是虚拟机运行在Client模式下的默认新生代收集器。
简单而高效(与其他收集器的单线程比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。
Serial / Serial Old 收集器运行示意图
Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。
同上。
ParNew / Serial Old 收集器运行示意图
ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一样,在实现上,这两种收集器也共用了相当多的代码。
ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器。
很重要的原因是:除了Serial收集器外,目前只有它能与CMS收集器配合工作。 在JDK 1.5时期,HotSpot推出了一款在强交互应用中几乎可认为有划时代意义的垃圾收集器——CMS收集器,这款收集器是HotSpot虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作。 不幸的是,CMS作为老年代的收集器,却无法与JDK 1.4.0中已经存在的新生代收集器Parallel Scavenge配合工作,所以在JDK 1.5中使用CMS来收集老年代的时候,新生代只能选择ParNew或者Serial收集器中的一个。
Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器。
停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
XX:+UseAdaptiveSizePolicy
。当这个参数打开之后,就不需要手工指定新生代的大小、Eden与Survivor区的比例、晋升老年代对象年龄等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics)。Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。
在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器。
这个收集器是在JDK 1.6中才开始提供的,在此之前,新生代的Parallel Scavenge收集器一直处于比较尴尬的状态。原因是,如果新生代选择了Parallel Scavenge收集器,老年代除了Serial Old收集器外别无选择(Parallel Scavenge收集器无法与CMS收集器配合工作)。由于老年代Serial Old收集器在服务端应用性能上的“拖累”,使用了Parallel Scavenge收集器也未必能在整体应用上获得吞吐量最大化的效果,由于单线程的老年代收集中无法充分利用服务器多CPU的处理能力,在老年代很大而且硬件比较高级的环境中,这种组合的吞吐量甚至还不一定有ParNew加CMS的组合“给力”。直到Parallel Old收集器出现后,“吞吐量优先”收集器终于有了比较名副其实的应用组合。
同上。
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。
CMS收集器是基于“标记—清除”算法实现的,它的运作过程相对于前面几种收集器来说更复杂一些,整个过程分为4个步骤: 1. 初始标记(CMS initial mark) 初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,需要“Stop The World”。
Concurrent Mark Sweep 收集器运行示意图
由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
G1(Garbage-First)是一款面向服务端应用的垃圾收集器。HotSpot开发团队赋予它的使命是未来可以替换掉JDK 1.5中发布的CMS收集器。
G1 收集器运行示意图
G1收集器的运作大致可划分为以下几个步骤:
Remembered Set Logs
里面,最终标记阶段需要把Remembered Set Logs
的数据合并到Remembered Set
中,这阶段需要停顿线程,但是可并行执行。与其他GC收集器相比,G1具备如下优点:
在G1之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。
G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(这也就是Garbage-First名称的来由)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。
垃圾收集相关的常用参数:
参数 | 描述 |
---|---|
UseSerialGC | 虚拟机运行在Client模式下的默认值,打开此开关后,使用 Serial + Serial Old的收集器组合进行内存回收 |
UseParNewGC | 打开此开关后,使用ParNew + Serial Old的收集器组合进行内存回收 |
UseConcMarkSweepGC | 打开此开关后,使用ParNew + CMS + Serial Old的收集器组合进行内存回收。Serial Old作为CMS出现Concurrent Mode Failure失败后的后备收集器使用 |
UseParallelGC | 虚拟机运行在Server模式下的默认值,打开此开关后,使用Parallel Scanvenge + Serial Old (PS MarkSweep)的收集器组合进行内存回收 |
UseParallelOldGC | 打开此开关后,使用 Parallel Scavenge + Parallel Old的收集器组合进行内存回收 |
SurvivorRatio | 新生代中Eden区域与Survivor区域的容量比值,默认为8,代表Eden:Survivor = 8:1 |
PretenureSizeThreshold | 直接晋级到老年代的对象大小,设置后,大于这个参数的对象将直接在老年代中分配 |
MaxTenuringThreshold | 晋升到老年代的对象年龄。每个对象坚持过一次Minor GC之后,年龄就增加1,当超过这个参数值以后就进入老年代 |
UseAdaptiveSizePolicy | 动态调整Java堆中各个区域的大小以及进入老年代的年龄 |
HandlePromotionFailure | 是否允许担保失败,即老年代的剩余空间不足以应付新生代的整个Eden和Survivor区所有对象都存活的极端情况 |
ParallelGCThreads | 设置并行GC时进行内存回收的线程数 |
GCTimeRatio | GC时间占总时间的比率,默认值99,即允许1%的GC时间。仅在使用Parallel Scavenge收集器时生效 |
MaxGCPauseMillis | 设置GC的最大停顿时间。仅在使用Parallel Scavenge收集器时生效 |
CMSInitiatingOccupancyFraction | 设置CMS收集器在老年代空间被使用多少后触发垃圾收集。默认值为68%,仅在CMS收集器时生效 |
UseCMSCompactAtFullCollection | 设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片整理。仅在CMS收集器时生效 |
CMSFullGCsBeforeCompaction | 设置CMS收集器在进行若干次垃圾收集后再启动一次碎片整理。仅在CMS收集器时生效 |
7种垃圾收集器对比:
类型 | 特性 | 算法 | 适用场景 | 优缺点 |
---|---|---|---|---|
Serial | 单线程收集器 | 复制算法 | Serial收集器是虚拟机运行在Client模式下的默认新生代收集器。 | 优势:简单而高效 缺点:需要暂停其他工作线程(Stop The World) |
Serial Old | Serial收集器的老年代版本,也是一个单线程收集器 | 标记-整理算法 | **Client模式:**Serial Old收集器的主要意义也是在于给Client模式下的虚拟机使用。 **Server模式:**JDK1.5前与P/S收集器搭配使用;作为CMS收集器的后备预案 | 同上 |
ParNew | Serial收集器的多线程版本 | 复制算法 | 在Server模式下的虚拟机中首选的新生代收集器,能与CMS收集器配合工作 | 缺点:单CPU情况下效果没有Seria收集器好优点:多CPU情况下对系统资源利用充分 |
Parallel Scavenge | 新生代收集器,并行的多线程收集器 | 复制算法 | 适合在后台运算而不需要太多交互的任务。 | 优点:高吞吐量、自适应调节策略 |
Parallel Old | Parallel Scavenge收集器的老年代版本、多线程 | 标记-整理算法 | 适合注重吞吐量以及CPU资源敏感的场合 | 同上 |
CMS | 最短回收停顿时间 | 标记-清除算法 | 适合注重服务响应速度的场合,如互联网站或B/S系统服务端 | 优点: 并发收集、低停顿。缺点:会降低吞吐量、无法处理浮动垃圾、会产生大量空间碎片 |
G1 | Java堆收集器、为替换CMS而生、并发 | 标记—整理算法复制算法 | 面向服务端应用的垃圾收集器 | 优点:可预测的停顿、无空间碎片缺点:待续 |
虽然我们是在对各个收集器进行比较,但并非为了挑选出一个最好的收集器。因为直到现在为止还没有最好的收集器出现,更加没有万能的收集器,所以我们选择的只是对具体应用最合适的收集器。这点不需要多加解释就能证明:如果有一种放之四海皆准、任何场景下都适用的完美收集器存在,那HotSpot虚拟机就没必要实现那么多不同的收集器了。