JVM垃圾回收(三)-7种垃圾收集器
概述
垃圾收集器是垃圾回收算法(标记-清除算法、复制算法、标记-整理算法)的具体实现,不同商家、不同版本的JVM所提供的垃圾收集器可能会有很在差别,本文主要介绍HotSpot虚拟机中的垃圾收集器。7种垃圾收集器如图所示。
图中表示7种作用于不同分代的收集器,如果两个收集器之间存在连线,说明可以搭配使用。横线上办部分为年轻代的垃圾收集器,下半部分为老年代的垃圾收集器。
相关概念
STW机制
Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起(除了垃圾收集帮助器之外)。Java中一种全局暂停现象,全局停顿,所有Java代码停止,native代码可以执行,但不能与JVM交互;这些现象多半是由于gc引起。
吞吐量
指的是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即
吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)。
假设虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
并行和并发
- 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
- 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行。而垃圾收集程序运行在另一个CPU上。
Minor GC 和 Full GC
- 新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。
- 老年代GC(Major GC / Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。
Serial收集器
收集器流程图如图所示
特点:单线程的收集器,在工作时,必须暂停其他所有的工作线程,直至它收集结束。也是虚拟机默认的新生代收集器
优点:
- 单线程执行,没有线程交互的开销,
- 垃圾收集停顿在几百毫秒内,这一定程度上可以接受
缺点:STW问题(“Stop The World”)
ParNew收集器
ParNew收集器就是Serial收集器多线程的版本。 除了多线程外,其余的行为、特点和Serial收集器一样;
在Server模式下,ParNew收集器是一个非常重要的收集器,因为除Serial外,目前只有它能与CMS收集器配合工作;
但在单个或两个CPU环境中,不会比Serail收集器有更好的效果,因为存在线程交互开销。
设置参数
- "-XX:+UseConcMarkSweepGC":指定使用CMS后,会默认使用ParNew作为新生代收集器;
- "-XX:+UseParNewGC":强制指定使用ParNew;
- "-XX:ParallelGCThreads":指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同;
parallel scavenger收集器
parallel scavenger收集器是达到一个可控制吞吐量。既尽量的缩短垃圾收集时间,保证与用户的交互速度。既给定一个指定的垃圾收集停顿收集,当垃圾收集时间超过给定值,无论是否回收完成都会停止回收工作。
特点:
- 新生代收集器
- 与ParNew一样 并行的多线程收集器
设置参数
- -XX:MaxGCPauseMillis
控制最大垃圾收集停顿时间,大于0的毫秒数
MaxGCPauseMillis设置得稍小,停顿时间可能会缩短,但也可能会使得吞吐量下降。因为可能导致垃圾收集发生得更频繁 - -XX:GCTimeRatio
设置垃圾收集时间占总时间的比率,0<n<100的整数
GCTimeRatio相当于设置吞吐量大小 - -XX:+UseAdptiveSizePolicy
这是一个开关参数,打开参数后,就不需要手工指定新生代的大小(-Xmn)、Eden和Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种方式称为GC自适应的调节策略(GC Ergonomics)。自适应调节策略也是Parallel Scavenge收集器与ParNew收集器的一个重要区别。
Serial Old收集器
Serial Old 是 Serial收集器的老年代版本,它同样是一个单线程收集器,使用 “标记-整理”(Mark-Compact) 算法。
此收集器的主要意义也是在于给Client模式下的虚拟机使用。如果在Server模式下,它还有两大用途:
- 在JDK1.5 以及之前版本(Parallel Old诞生以前)中与Parallel Scavenge收集器搭配使用
- 作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用
Parallel Old收集器
Parallel Old收集器是parallel scavenger收集器的老年代版本。
特点:
- 采用“标记-整理”算法
- 多线程执行
- Parallel Old收集器的工作流程与Parallel Scavenge相同
CMS收集器
CMS收集器是一种以获取最短回收停顿时间为目标的收集器。CMS收集器基于“标记-清除”算法。它分为4个步骤
- 初始标记(会产生STW机制,标记一下GC Roots能直接关联到的对象)
- 并发标记(进行GC RootsTracing的过程)
- 重新标记(会产生STW机制,为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录)
- 并发清除
其中耗时最长的并发标记和并发清除过程可以与用户线程一起工作。执行过程如图所示
优点:发收集、低停顿,因此CMS收集器也被称为并发低停顿收集器(Concurrent Low Pause Collector)
缺点
- 并发收集虽然不会暂停用户线程,但因为占用一部分CPU资源,还是会导致应用程序变慢,总吞吐量降低。
CMS的默认收集线程数量是=(CPU数量+3)/4;
当CPU数量多于4个,收集线程占用的CPU资源多于25%,对用户程序影响可能较大;不足4个时,影响更大,可能无法接受。。 - 产生浮动垃圾
在并发清除时,用户线程新产生的垃圾,称为浮动垃圾;这使得并发清除时需要预留一定的内存空间,不能像其他收集器在老年代几乎填满再进行收集;也要可以认为CMS所需要的空间比其他垃圾收集器大;
"-XX:CMSInitiatingOccupancyFraction":设置CMS预留内存空间;
JDK1.5默认值为68%;
JDK1.6变为大约92%; - 标记-清除算法导致产生大量内存碎片
产生大量不连续的内存碎片会导致分配大内存对象时,无法找到足够的连续内存,从而需要提前触发另一次Full GC动作。
解决方法: - "-XX:+UseCMSCompactAtFullCollection"
使得CMS出现上面这种情况时不进行Full GC,而开启内存碎片的合并整理过程;
但合并整理过程无法并发,停顿时间会变长;
默认开启(但不会进行,结合下面的CMSFullGCsBeforeCompaction); - "-XX:+CMSFullGCsBeforeCompaction"
设置执行多少次不压缩的Full GC后,来一次压缩整理;
为减少合并整理过程的停顿时间;
默认为0,也就是说每次都执行Full GC,不会进行压缩整理;
G1收集器
特点:
并行与并发:
- 能充分利用多CPU、多核环境下的硬件优势
- 可以并行来缩短"Stop The World"停顿时间
- 也可以并发让垃圾收集与用户程序同时进行
分代收集:
- 不需要搭配其他收集器进行配合使用
- 采用不同的方式处理不同的对象
空间整合,不产生碎片
- 从整体看,是基于标记-整理算法
- 从局部(两个Region间)看,是基于复制算法
- 可预测的停顿:低停顿的同时实现高吞吐量
总结
收集器 | 串行、并行or并发 | 新生代/老年代 | 算法 | 目标 | 适用场景 |
---|---|---|---|---|---|
Serial | 串行 | 新生代 | 复制算法 | 响应速度优先 | 单CPU环境下的Client模式 |
Serial Old | 串行 | 老年代 | 标记-整理 | 响应速度优先 | 单CPU环境下的Client模式、CMS的后备预案 |
ParNew | 并行 | 新生代 | 复制算法 | 响应速度优先 | 多CPU环境时在Server模式下与CMS配合 |
Parallel Scavenge | 并行 | 新生代 | 复制算法 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 |
Parallel Old | 并行 | 老年代 | 标记-整理 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 |
CMS | 并发 | 老年代 | 标记-清除 | 响应速度优先 | 集中在互联网站或B/S系统服务端上的Java应用 |
G1 | 并发 | both | 标记-整理+复制算法 | 响应速度优先 | 面向服务端应用,将来替换CMS |