垃圾收集器组合
Serial+Serial Old
Serial+CMS
ParNew+CMS
ParNew+Serial Old
Paralle Scavenge + Serial Old
Paralle Scavenge + Paralle Old
一.Serial(年轻代) :
- 年轻代收集器,可以和Serial Old、CMS组合使用
- 采用复制算法
- 使用单线程进行垃圾回收,回收时会导致Stop The World,用户进程停止
- client模式年轻代默认算法
- GC日志关键字:DefNew(Default New Generation)
- 新生代收集器,可以和Serial Old、CMS组合使用
- 采用复制算法
- 使用多线程进行垃圾回收,回收时会导致Stop The World,其它策略和Serial一样
- server模式年轻代默认算法
- 使用-XX:ParallelGCthreads参数来限制垃圾回收的线程数
- GC日志关键字:ParNew(Parallel New Generation)
- 图示(ParNew + Serail Old)
3、Paralle Scavenge(年轻代)
- 新生代收集器,可以和Serial Old、Parallel组合使用,不能和CMS组合使用
- 采用复制算法
- 使用多线程进行垃圾回收,回收时会导致Stop The World
- 关注系统吞吐量
- -XX:MaxGCPauseMillis:设置大于0的毫秒数,收集器尽可能在该时间内完成垃圾回收
- -XX:GCTimeRatio:大于0小于100的整数,即垃圾回收时间占总时间的比率,设置越小则希望垃圾回收所占时间越小,CPU能花更多的时间进行系统操作,提高吞吐量
- -XX:UseAdaptiveSizePolicy:参数开关,启动后系统动态自适应调节各参数,如-Xmn、-XX:SurvivorRatio等参数,这是和ParNew收集器重要的区别
- GC日志关键字:PSYoungGen
- 年老代收集器,可以和所有的年轻代收集器组合使用(Serial收集器的年老代版本)
- 采用 ”标记-整理“算法,会对垃圾回收导致的内存碎片进行整理
- 使用单线程进行垃圾回收,回收时会导致Stop The World,用户进程停止
- GC日志关键字:Tenured
- 图示(Serial+Serial Old)
5.Parallel Old
- 年老代收集器,只能和Parallel Scavenge组合使用(Parallel Scavenge收集器的年老代版本)
- 采用 ”标记-整理“算法,会对垃圾回收导致的内存碎片进行整理
- 关注吞吐量的系统可以将Parallel Scavenge+Parallel Old组合使用
- GC日志关键字:ParOldGen
- 图示(Parallel Scavenge+Parallel Old)
- 年老代收集器,可以和Serial、ParNew组合使用
- 采用 ”标记-清除“算法,可以通过设置参数在垃圾回收时进行内存碎片的整理
1、UserCMSCompactAtFullCollection:默认开启,FullGC时进行内存碎片整理,整理时用户进程需停止,即发生Stop The World
2、CMSFullGCsBeforeCompaction:设置执行多少次不压缩的Full GC后,执行一个带压缩的(默认为0,表示每次进入Full GC时都进行碎片整理) - CMS是并发算法,表示垃圾回收和用户进行同时进行,但是不是所有阶段都同时进行,在初始标记、重新标记阶段还是需要Stop the World。CMS垃圾回收分这四个阶段
1、初始标记(CMS Initial mark) Stop the World 仅仅标记一下GC Roots能直接关联到的对象,速度快
2、并发标记(CMS concurrent mark) 进行GC Roots Tracing,时间长,不发生用户进程停顿
3、重新标记(CMS remark) Stop the World 修正并发标记期间因用户程序继续运行导致标记变动的那一部分对象的标记记录,停顿时间较长,但远比并发标记时间短
4、并发清除(CMS concurrent sweep) 清除的同时用户进程会导致新的垃圾,时间长,不发生用户进程停顿 - 适合于对响应时间要求高的系统
- GC日志关键字:CMS-initial-mark、CMS-concurrent-mark-start、CMS-concurrent-mark、CMS-concurrent-preclean-start、CMS-concurrent-preclean、CMS-concurrent-sweep、CMS-concurrent-reset等等
- 缺点
1、对CPU资源非常敏感
2、CMS收集器无法处理浮动垃圾,即清除时用户进程同时产生的垃圾,只能等到下次GC时回收
3、因为是使用“标记-清除”算法,所以会产生大量碎片 - 图示
概述如果说前面介绍的收集算法(JVM之垃圾回收-垃圾收集算法)是内存回收的抽象策略,那么垃圾收集器就是内存回收的具体实现。
JVM规范对于垃圾收集器的应该如何实现没有任何规定,因此不同的厂商、不同版本的虚拟机所提供的垃圾收集器差别较大,这里只看HotSpot虚拟机。
就像没有最好的算法一样,垃圾收集器也没有最好,只有最合适。我们能做的就是根据具体的应用场景选择最合适的垃圾收集器。
Serial收集器Serial(串行)收集器收集器是最基本、历史最悠久的垃圾收集器了(新生代采用复制算法,老生代采用标志整理算法)。大家看名字就知道这个收集器是一个单线程收集器了。
它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( “Stop The World” :将用户正常工作的线程全部暂停掉),直到它收集结束。看图理解:
上图中:
新生代采用复制算法,Stop-The-World老年代采用标记-整理算法,Stop-The-World当它进行GC工作的时候,虽然会造成Stop-The-World,正如每种算法都有存在的原因,该串行收集器也有存在的原因:因为简单而高效(与其他收集器的单线程比),对于限定单个CPU的环境来说,没有线程交互的开销,专心做GC,自然可以获得最高的单线程效率。所以Serial收集器对于运行在client模式下的应用是一个很好的选择(到目前为止,它依然是虚拟机运行在client模式下的默认新生代收集器)串行收集器的缺点很明显,虚拟机的开发者当然也是知道这个缺点的,所以一直都在缩减Stop The World的时间。在后续的垃圾收集器设计中停顿时间在不断缩短(但是仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)
整理一下前面关于Serial收集器的知识
特点针对新生代的收集器;采用复制算法;单线程收集;进行垃圾收集时,必须暂停所有工作线程,直到完成;即会"Stop The World";应用场景依然是HotSpot在Client模式下默认的新生代收集器;也有优于其他收集器的地方:简单高效(与其他收集器的单线程相比);对于限定单个CPU的环境来说,Serial收集器没有线程交互(切换)开销,可以获得最高的单线程收集效率;在用户的桌面应用场景中,可用内存一般不大(几十M至一两百M),可以在较短时间内完成垃圾收集(几十MS至一百多MS),只要不频繁发生,这是可以接受的设置参数添加该参数来显式的使用串行垃圾收集器:"-XX:+UseSerialGC"12ParNew收集器(Serial收集器的多线程版本-使用多条线程进行GC)ParNew收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和Serial收集器完全一样。它是许多运行在Server模式下的虚拟机的首要选择,除了Serial收集器外,目前只有它能与CMS收集器配合工作。CMS收集器是一个被认为具有划时代意义的并发收集器,因此如果有一个垃圾收集器能和它一起搭配使用让其更加完美,那这个收集器必然也是一个不可或缺的部分了。收集器的运行过程如下图所示:
特点除了多线程外,其余的行为、特点和Serial收集器一样;如Serial收集器可用控制参数、收集算法、Stop The World、内存分配规则、回收策略等;Serial收集器共用了不少代码;应用场景在Server模式下,ParNew收集器是一个非常重要的收集器,因为除Serial外,目前只有它能与CMS收集器配合工作;但在单个CPU环境中,不会比Serail收集器有更好的效果,因为存在线程交互开销。
设置参数指定使用CMS后,会默认使用ParNew作为新生代收集:"-XX:+UseConcMarkSweepGC"强制指定使用ParNew: "-XX:+UseParNewGC"指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相:"-XX:ParallelGCThreads"123456为什么只有ParNew能与CMS收集器配合CMS是HotSpot在JDK1.5推出的第一款真正意义上的并发(Concurrent)收集器,第一次实现了让垃圾收集线程与用户线程(基本上)同时工作;CMS作为老年代收集器,但却无法与JDK1.4已经存在的新生代收集器Parallel Scavenge配合工作;因为Parallel Scavenge(以及G1)都没有使用传统的GC收集器代码框架,而另外独立实现;而其余几种收集器则共用了部分的框架代码;Parallel Scavenge收集器Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器。
Parallel Scavenge收集器关注点是吞吐量(如何高效率的利用CPU)。CMS等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。(吞吐量:CPU用于用户代码的时间/CPU总消耗时间的比值,即=运行用户代码的时间/(运行用户代码时间+垃圾收集时间)。比如,虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。)
运行示意图:
Parallel Scavenge收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,不进行手工优化,可以选择把内存管理优化交给虚拟机去完成。
特点新生代收集器;采用复制算法;多线程收集;CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间;而Parallel Scavenge收集器的目标则是达一个可控制的吞吐量(Throughput);应用场景高吞吐量为目标,即减少垃圾收集时间,让用户代码获得更长的运行时间;当应用程序运行在具有多个CPU上,对暂停时间没有特别高的要求时,即程序主要在后台进行计算,而不需要与用户进行太多交互;例如,那些执行批量处理、订单处理(对账等)、工资支付、科学计算的应用程序;设置参数Parallel Scavenge收集器提供两个参数用于精确控制吞吐量:
控制最大垃圾收集停顿时间"-XX:MaxGCPauseMillis"1控制最大垃圾收集停顿时间,大于0的毫秒数;MaxGCPauseMillis设置得稍小,停顿时间可能会缩短,但也可能会使得吞吐量下降;因为可能导致垃圾收集发生得更频繁;设置垃圾收集时间占总时间的比率"-XX:GCTimeRatio"1设置垃圾收集时间占总时间的比率,0 < n < 100的整数;GCTimeRatio相当于设置吞吐量大小;垃圾收集执行时间占应用程序执行时间的比例的计算方法是: 1 / (1 + n) 。例如,选项-XX:GCTimeRatio=19,设置了垃圾收集时间占总时间的5% = 1/(1+19);默认值是1% = 1/(1+99),即n=99;
垃圾收集所花费的时间是年轻一代和老年代收集的总时间;如果没有满足吞吐量目标,则增加代的内存大小以尽量增加用户程序运行的时间;
GC自适应的调节策略(GC Ergonomics)另外还有一个参数:
"-XX:+UseAdptiveSizePolicy" 1开启这个参数后,就不用手工指定一些细节参数,如:
新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、晋升老年代的对象年龄(-XX:PretenureSizeThreshold)等;JVM会根据当前系统运行情况收集性能监控信息,动态调整这些参数,以提供最合适的停顿时间或最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics);这是一种值得推荐的方式:(1)、只需设置好内存数据大小(如"-Xmx"设置最大堆);(2)、然后使用"-XX:MaxGCPauseMillis"或"-XX:GCTimeRatio"给JVM设置一个优化目标;(3)、那些具体细节参数的调节就由JVM自适应完成;这也是Parallel Scavenge收集器与ParNew收集器一个重要区别;Serial Old收集器Serial收集器的老年代版本,它同样是一个单线程收集器。它主要有两大用途:一种用途是在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用,另一种用途是作为CMS收集器的后备方案
特点针对老年代;采用"标记-整理-压缩"算法(Mark-Sweep-Compact);单线程收集;Serial/Serial Old收集器运行示意图在前面有。应用场景主要用于Client模式;而在Server模式有两大用途:(A)、在JDK1.5及之前,与Parallel Scavenge收集器搭配使用(JDK1.6有Parallel Old收集器可搭配Parallel Scavenge收集器);(B)、作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用;Parallel Old收集器Parallel Scavenge收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源的场合,都可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器。在JDK1.6才有的。
特点针对老年代;采用"标记-整理-压缩"算法;多线程收集;Parallel Scavenge/Parallel Old收集器运行示意图如下:
应用场景JDK1.6及之后用来代替老年代的Serial Old收集器;特别是在Server模式,多CPU的情况下;这样在注重吞吐量以及CPU资源敏感的场景,就有了Parallel Scavenge(新生代)加Parallel Old(老年代)收集器的"给力"应用组合;设置参数指定使用Parallel Old收集器:"-XX:+UseParallelOldGC"12CMS(Concurrent Mark Sweep)收集器CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常适合在注重用户体验的应用上使用。
特点针对老年代基于"标记-清除"算法(不进行压缩操作,会产生内存碎片)以获取最短回收停顿时间为目标并发收集、低停顿需要更多的内存CMS是HotSpot在JDK1.5推出的第一款真正意义上的并发(Concurrent)收集器;第一次实现了让垃圾收集线程与用户线程(基本上)同时工作;
应用场景与用户交互较多的场景;(如常见WEB、B/S-浏览器/服务器模式系统的服务器上的应用)希望系统停顿时间最短,注重服务的响应速度;以给用户带来较好的体验;CMS收集器运作过程从名字中的Mark Sweep这两个词可以看出,CMS收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程可分为四个步骤:
初始标记: 暂停所有的其他线程,初始标记仅仅标记GC Roots能直接关联到的对象,速度很快;并发标记并发标记就是进行GC Roots Tracing的过程;同时开启GC和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以GC线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方;重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录(采用多线程并行执行来提升效率);需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短;并发清除: 开启用户线程,同时GC线程开始对为标记的区域做清扫,回收所有的垃圾对象;由于整个过程耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作。所以总体来说,CMS的内存回收是与用户线程一起“并发”执行的。
CMS收集器运行示意图如下:
设置参数指定使用CMS收集器"-XX:+UseConcMarkSweepGC"12缺点(一)对CPU资源敏感面向并发设计的程序都对CPU资源比较敏感(并发程序的特点)。在并发阶段,它虽然不会导致用户线程停顿,但会因为占用了一部分线程(或者说CPU资源)而导致应用程序变慢,总吞吐量会降低。(在对账系统中,不适合使用CMS收集器)。
CMS的默认收集线程数量是=(CPU数量+3)/4; 当CPU数量越多,回收的线程占用CPU就少。也就是当CPU在4个以上时,并发回收时垃圾收集线程不少于25%的CPU资源,对用户程序影响可能较大;不足4个时,影响更大,可能无法接受。(比如 CPU=2时,那么就启动一个线程回收,占了50%的CPU资源。)(一个回收线程会在回收期间一直占用CPU资源)
针对这种情况,曾出现了"增量式并发收集器"(Incremental Concurrent Mark Sweep/i-CMS);类似使用抢占式来模拟多任务机制的思想,让收集线程和用户线程交替运行,减少收集线程运行时间;但效果并不理想,JDK1.6后就官方不再提倡用户使用。
(二)无法处理浮动垃圾无法处理浮动垃圾,可能出现"Concurrent Mode Failure"失败在并发清除时,用户线程新产生的垃圾,称为浮动垃圾;
解决办法这使得并发清除时需要预留一定的内存空间,不能像其他收集器在老年代几乎填满再进行收集;也可以认为CMS所需要的空间比其他垃圾收集器大; 可以使用"-XX:CMSInitiatingOccupancyFraction",设置CMS预留老年代内存空间; (详解见名词解释)
(三)产生大量内存碎片由于CMS是基于“标记+清除”算法来回收老年代对象的,因此长时间运行后会产生大量的空间碎片问题,可能导致新生代对象晋升到老生代失败。
由于碎片过多,将会给大对象的分配带来麻烦。因此会出现这样的情况,老年代还有很多剩余的空间,但是找不到连续的空间来分配当前对象,这样不得不提前触发一次Full GC。
解决办法使用"-XX:+UseCMSCompactAtFullCollection"和"-XX:+CMSFullGCsBeforeCompaction",需要结合使用。
UseCMSCompactAtFullCollection"-XX:+UseCMSCompactAtFullCollection"1为了解决空间碎片问题,CMS收集器提供−XX:+UseCMSCompactAlFullCollection标志,使得CMS出现上面这种情况时不进行Full GC,而开启内存碎片的合并整理过程;
但合并整理过程无法并发,停顿时间会变长;默认开启(但不会进行,需要结合CMSFullGCsBeforeCompaction使用);CMSFullGCsBeforeCompaction由于合并整理是无法并发执行的,空间碎片问题没有了,但是有导致了连续的停顿。因此,可以使用另一个参数−XX:CMSFullGCsBeforeCompaction,表示在多少次不压缩的Full GC之后,对空间碎片进行压缩整理。
可以减少合并整理过程的停顿时间;默认为0,也就是说每次都执行Full GC,不会进行压缩整理;
由于空间不再连续,CMS需要使用可用"空闲列表"内存分配方式,这比简单实用"碰撞指针"分配内存消耗大;
CMS&Parallel Old总体来看,CMS与Parallel Old垃圾收集器相比,CMS减少了执行老年代垃圾收集时应用暂停的时间;
但却增加了新生代垃圾收集时应用暂停的时间、降低了吞吐量而且需要占用更大的堆空间;(原因:CMS不进行内存空间整理节省了时间,但是可用空间不再是连续的了,垃圾收集也不能简单的使用指针指向下一次可用来为对象分配内存的地址了。相反,这种情况下,需要使用可用空间列表。即,会创建一个指向未分配区域的列表,每次为对象分配内存时,会从列表中找到一个合适大小的内存区域来为新对象分配内存。这样做的结果是,老年代上的内存的分配比简单实用碰撞指针分配内存消耗大。这也会增加年轻代垃圾收集的额外负担,因为老年代中的大部分对象是在新生代垃圾收集的时候从新生代提升为老年代的。)当新生代对象无法分配过大对象,就会放到老年代进行分配。