7种JVM垃圾回收器详解 & 垃圾收集参数汇总

  • 1. jvm垃圾回收器
    • 1.1. Serial收集器
    • 1.2. ParNew收集器
    • 1.3. Parallel Scavenge收集器
    • 1.4. Serial Old收集器
    • 1.5. Parallel Old收集器
    • 1.6. CMS收集器
    • 1.7. G1收集器
    • 1.8. 总结
  • 2. 垃圾收集器参数汇总

1. jvm垃圾回收器

本篇文章主要介绍下图7种垃圾回收器,相关的垃圾回收算法可以看一下之前的文章。只有在新生代和老年代垃圾回收器之间有连线,才可以搭配使用,比如老年代垃圾回收器设置的是CMS,新生代可以使用Serial、ParNew。

7种JVM垃圾回收器详解 & 垃圾收集参数汇总_第1张图片

在开始介绍垃圾回收器之前,先介绍两个名词:并发、并行。

从ParNew收集器开始,后面还会接触到几款并发和并行的收集器。在大家可能产生疑惑之前,有必要先解释清楚。这两个名词都是并发编程中的概念,
在谈论垃圾收集器的上下文语境中,它们可以解释如下:

  • 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
  • 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续运行,
    而垃圾收集程序运行于另一个CPU上。

Minor GC和Full GC的区别

  • Minor GC:又称新生代GC,指发生在新生代的垃圾收集动作;因为Java对象大多是朝生夕灭,所以Minor GC非常频繁,一般回收速度也比较快;

  • Full GC:又称为Major GC或老年代GC,指发生在老年代的GC;出现Full GC经常会伴随至少一次的Minor GC(不是绝对,
    Parallel Scavenge收集器就可以选择设置Major GC策略);Major GC速度一般比Minor GC慢10倍以上。

1.1. Serial收集器

串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。新生代、老年代使用串行回收;
新生代复制算法、老年代标记-压缩;垃圾收集的过程中会Stop The World(服务暂停),暂停其他所有的工作线程。

参数控制:

-XX:+UseSerialGC 开启此参数使用Serial & Serial Old搜集器(client模式默认值)

7种JVM垃圾回收器详解 & 垃圾收集参数汇总_第2张图片

1.2. ParNew收集器

ParNew收集器 ParNew收集器其实就是Serial收集器的多线程版本。新生代并行,老年代串行;新生代复制算法、老年代标记-压缩。
目前新生代除了Serial,只有ParNew可以配合老年代的CMS收集器进行工作,当使用-XX:+UseConcMarkSweepGC选项后的默认新生代收集器就是ParNew。

参数控制:

-XX:+UseParNewGC 开启此参数使用ParNew & Serial Old收集器(不推荐)
-XX:+ParallelGCThreads 设置并行GC时进行内存回收的线程数

7种JVM垃圾回收器详解 & 垃圾收集参数汇总_第3张图片

1.3. Parallel Scavenge收集器

Parallel Scavenge收集器类似ParNew收集器,是一个新生代收集器,同时也是使用复制算法的收集器,又是并行的多线程收集器,
看上去和ParNew是一样的,那么它有什么特别之处呢?

CMS收集器的关注点尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器更关注系统的吞吐量(Throughput)。
所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间),
虚拟机总共运行100分钟,垃圾收集花掉1分钟,那么吞吐量就是99%。

停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,
主要适合在后台运算而不需要太多交互的任务。

Parallel Scavenge收集器提供了两个参数用户精确控制吞吐量,
分别是控制最大垃圾收集停顿时间的 -XX:MaxGCPauseMillis 参数以及直接设置吞吐量大小的 -XX:GCTimeRatio 参数。

  • MaxGCPauseMillis:参数允许的值是一个大于0的毫秒数,收集器将尽可能地保证内存回收花费的时间不超过设定值。
    如果把这个参数设置得小一点就能使系统得垃圾收集速度变得更快,但是GC停顿时间缩短是以牺牲吞吐量和新生代空间来换取的。
    缩短GC停顿时间,系统会把新生代调小一些,收集300MB新生代肯定比收集500MB快,这直接导致垃圾收集发生得更频繁一些,原先10秒收集一次、每次停顿100毫秒,现在变成5秒收集一次、每次停顿70毫秒。停顿时间的确在下降,但吞吐量也降下来了。

  • GCTimeRatio:参数的值应当是一个大于0且小于100的整数,垃圾收集时间比作1,运行用户代码时间为1-99的整数,GCTimeRatio = 运行用户代码时间 / 垃圾收集时间
    如果把此参数设置为19,那允许的最大GC时间就占总时间的5%(即1/(1+19)),默认值为99,就是允许最大1%(即1/(1+99))的垃圾收集时间。
    使用参数的理论效果:GCTimeRatio越大,吞吐量越大,GC的总耗时越小,有可能导致单次MinorGC耗时变长。适用于高运算场景。

因此Parallel Scavenge收集器也经常称为“吞吐量优先”收集器。Parallel Scavenge收集器还有一个参数-XX:+UseAdptiveSizePolicy,
这是一个开关参数,当开关打开,就不需要手工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、
晋升老年代对象大小(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,
动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics)。如果读者不清楚收集器运作,手工优化存在困难,可以使用自适应调节策略,只需要设置好基本的内存数据(如 -Xmx 设置最大堆),
然后使用MaxGCPauseMillis(更关注最大停顿时间)或GCTimeRatio(更关注吞吐量)参数给虚拟机设立一个优化目标,
具体的细节参数的调节工作就由虚拟机来完成。自适应调节策略,是Parallel Scavenge收集器与ParNew收集器的一个重要区别。

参数控制:

-XX:+UseParallelGC 开启此参数使用parallel scavenge & parallel old收集器(server模式默认值)。
-XX:+MaxGCPauseMillis 设置垃圾回收的最大停顿时间。若无法满足设置值,则会优先缩小新生代大小,仍无法满足的话则会牺牲吞吐量。
-XX:+GCTimeRatio 设置系统的吞吐量。比如设为99,则GC时间比为1/1+99=1%,也就是要求吞吐量为99%。
如果此值设置过大, 即GC时间太少导致GC无法完成, JVM会压缩新生代的大小以适应此配置。
-XX:+UseAdptiveSizePolicy 自适应调节策略开关

1.4. Serial Old收集器

Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整理”算法。这个收集器的主要意义也是在于给Client模式下的虚拟机使用。
如果在Server模式下,那么它主要还有两大用途:一种用途是在JDK1.5以及之前的版本中与Parallel Scavenge收集器搭配使用,
另一种用途就是作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。Serial Old收集器的工作过程如图所示:

7种JVM垃圾回收器详解 & 垃圾收集参数汇总_第4张图片

1.5. Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK1.6中才开始提供的,在此之前,
新生代的Parallel Scavenge收集器一直处于比较尴尬的状态。原因是,如果新生代选择了Parallel Scavenge收集器,
老年代除了Serial Old(PS MarkSweep)收集器外别无选择。由于老年代Serial Old收集器在服务端应用性能上的“拖累”,
使用了Parallel Scavenge收集器也未必能在整体应用上获得吞吐量最大化的效果,由于单线程的老年代收集中无法充分利用服务器多CPU的处理能力,
在老年代很大而且硬件比较高级的环境中,这种组合的吞吐量甚至还不一定有ParNew加CMS的组合“给力”。

直到Parallel Old收集器出现后,“吞吐量优先”收集器终于有了比较名副其实的应用组合,在注重吞吐量以及CPU资源敏感的场合,
都可以优先考虑Parallel Scavenge加Parallel Old收集器。Parallel Old收集器的工作过程如图所示:

7种JVM垃圾回收器详解 & 垃圾收集参数汇总_第5张图片

参数控制:

-XX:+UseParallelOldGC 使用Parallel scavenge & Parallel old收集器进行垃圾回收

1.6. CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,
这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。

从名字(包括“Mark Sweep”)上就可以看出,CMS收集器是基于“标记-清除”算法实现的,它的运作过程相对于前面几种收集器来说更复杂一些,整个过程分为4个步骤,包括:

  • 初始标记(CMS initial mark)
  • 并发标记(CMS concurrent mark)
  • 重新标记(CMS remark)
  • 并发清除(CMS concurrent sweep)

其中,初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,
并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,
这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。

由于整个过程中耗时最长的并发标记和并发清除过程收集线程都可以与用户线程一起工作,所以,从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
通过下图可以比较清楚地看到CMS收集器的运作步骤中并发和需要停顿的时间。CMS收集器的工作过程如图所示:

7种JVM垃圾回收器详解 & 垃圾收集参数汇总_第6张图片

CMS是一款优秀的收集器,它的主要优点在名字上已经体现出来了:并发收集、低停顿。但CMS有以下3个明显的缺点:

  • CMS收集器对CPU资源非常敏感。在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程(或者说CPU资源)而导致应用程序变慢,总吞吐量会降低。
    CMS默认启动的回收线程数是(CPU数量+3)/4,也就是当CPU在4个以上时,并发回收时垃圾收集线程不少于25%的CPU资源,并且随着CPU数量的增加而下降。
    但是当CPU不足4个(譬如2个)时,CMS对用户程序的影响就可能变得很大,如果本来CPU负载就比较大,还分出一半的运算能力去执行收集器线程,
    就可能导致用户程序的执行速度忽然降低了50%。

  • CMS收集器无法处理浮动垃圾(Floating Garbage),可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。由于CMS并发清理阶段用户线程还在运行着,
    伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC时再清理掉。
    这一部分垃圾就称为“浮动垃圾”。因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间给并发收集时的程序运作使用。
    CMS收集器的启动阈值在JDK1.6中已经提升至92%,可以通过-XX:CMSInitiatingOccupancyFraction来调整触发的百分比。
    要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:临时启用Serial Old收集器
    来重新进行老年代的垃圾收集,这样停顿的时间就很长了。所以要合理设置-XX:CMSInitiatingOccupancyFraction的值,不要设置太高,否则会导致大量
    “Concurrent Mode Failure”失败,性能反而会降低。

  • 还有最后一个缺点,CMS是一款基于“标记-清除”算法实现的收集器,这意味着收集结束时会有大量空间碎片产生。空间碎片过多时,将会给大对象分配带来很大麻烦,
    往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。为了解决这个问题,
    CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection开关参数(默认就是开启的),用于在CMS收集器顶不住要进行FullGC时开启内存碎片的合并整理过程,
    内存整理的过程是无法并发的,空间碎片问题没有了,但停顿时间不得不变长。虚拟机设计者还提供了另外一个参数-XX:CMSFullGCsBeforeCompaction,
    这个参数是用于设置执行多次不压缩的Full GC后,跟着来一次带压缩的(默认值为0,表示每次进入Full GC时都进行碎片整理)。

参数控制:

-XX:+CMSInitiatingOccupancyFraction 设置CMS收集器在老年代空间被使用多少后触发垃圾收集。默认值为68%,仅在使用CMS收集器时生效
-XX:+UseCMSCompactAtFullCollection 设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片整理。仅在使用CMS收集器时生效
-XX:+CMSFullGCsBeforeCompaction 设置CMS收集器在进行若干次垃圾收集后再启动一次内存碎片整理。仅在使用CMS收集器时生效

1.7. G1收集器

G1(Garbage-First)收集器是当今收集器技术发展的最前沿成果之一。

G1是一款面向服务端应用的垃圾收集器。HotSpot开发团队赋予它的使命是(在比较长期的)未来可以替换掉JDK 1.5中发布的CMS收集器。与其他GC收集器相比,
G1具备如下特点。

  • 并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短Stop-The-World停顿的时间,部分其他收集器原本需要停顿Java线程执行的GC动作,
    G1收集器仍然可以通过并发的方式让Java程序继续执行。
  • 分代收集:与其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,
    但它能够采用不同的方式取处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。
  • 空间整合:与CMS的“标记-清理”算法不同,G1从整体来看是基于“标记-整理”算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的,
    但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。
  • 可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。

在G1之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局就与其他收集器很很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的,它们都是一部分Region(不需要连续)的集合。

G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(这也就是Garbage-First名称的来由)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。

G1收集器的运作大致可划分为以下几个步骤:

  • 初始标记(Initial Marking)
  • 并发标记(Concurrent Marking)
  • 最终标记(Final Marking)
  • 筛选回收(Live Data Counting and Evacuation)

G1收集器的运作步骤中并发和需要停顿的阶段如下图所示:

7种JVM垃圾回收器详解 & 垃圾收集参数汇总_第7张图片
-XX:+MaxGCPauseMillis 设置垃圾回收的最大停顿时间。指定一个G1收集过程目标停顿时间,默认值200ms,不过它不是硬性条件,只是期望值。

1.8. 总结

垃圾收集器 并行(Parallel) 并发(Concurrent) 新生代回收 老年代回收 全堆回收 描述
Serial 单线程,使用复制算法,暂停所有工作线程
ParNew Serial收集器的多线程版本,使用复制算法,暂停所有工作线程
Parallel Scanvenge 多线程,“吞吐量优先”收集器,使用复制算法,暂停所有工作线程。可以精确控制吞吐量,还可以开启GC自适应的调节策略(GC Ergonomics)
Serial Old 单线程,Serial Old是Serial收集器的老年代版本,使用标记整理算法,暂停所有工作线程
Parallel Old 多线程,使用标记整理算法,和Parallel Scanvenge搭配使用,使吞吐量最大化
CMS 多线程,使用标记清除算法。并发收集,垃圾回收停顿时间短。但对CPU资源较敏感,CPU核心较少时会导致并发标记、并发清除时吞吐量骤减;无法处理浮动垃圾,老年代空间不能完全使用,需要预留一部分空间;由于基于标记清除算法,会产生大量空间碎片,将会给大对象分配带来很大麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC
G1 多线程,从局部(两个Region之间)来看,是基于复制算法,从整体来看是基于标记整理算法,不会产生空间碎片,相比于CMS,可预测停顿时间。适合大堆,追求低停顿

2. 垃圾收集器参数汇总

垃圾收集器 并行(Parallel)
UseSerialGC 虚拟运行在Client模式下的默认值,使用Serial & Serial Old收集器进行垃圾回收
UseParNewGC 使用ParNew & Serial Old收集器进行垃圾回收(不推荐)
UseConcMarkSweepGC 使用ParNew + CMS + Serial Old的收集器组合进行垃圾回收,Serial Old作为CMS出现Concurrent Mode Failure失败后的后备收集器
UseParallelGC 虚拟运行在Server模式下的默认值,使用Parallel scavenge & Serial old收集器进行垃圾回收
UseParallelOldGC 使用Parallel scavenge & Parallel old收集器进行垃圾回收
SurvivorRatio 新生代中Eden区域与Survivor区域的容量比值,默认为8,代表Eden:Survivor = 8:1
PretenureSizeThreshold 直接晋升到老年代的对象大小。默认为0,意味着任何对象都会先在新生代分配内存。如果设为10M,则超过10M的对象将不在eden区分配,而直接进入年老代。
MaxTenuringThreshold 晋升到老年代的对象年龄。每个对象在坚持过一次Minor GC之后,年龄就增加1,当超过这个参数值时就进入老年代
UseAdptiveSizePolicy 自适应调剂策略开关。打开,可以动态调整Java堆中各个区域的大小以及进入老年代的年龄
ParallelGCThreads 设置并行GC时进行内存回收的线程数
GCTimeRatio GC时间占总时间的比率,默认值为99,即允许比为1/(1+99)=1%的GC时间,吞吐量为99%。仅在使用Parallel scavenge收集器时生效
MaxGCPauseMillis 设置GC的最大停顿时间,仅在使用Parallel scavenge、G1收集器时生效
CMSInitiatingOccupancyFraction 设置CMS收集器在老年代空间被使用多少后触发垃圾收集。默认值为68%,仅在使用CMS收集器时生效
UseCMSCompactAtFullCollection 设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片整理。仅在使用CMS收集器时生效
CMSFullGCsBeforeCompaction 设置CMS收集器在进行若干次垃圾收集后再启动一次内存碎片整理。仅在使用CMS收集器时生效

引用:
1.《深入理解java虚拟机》 – 周志明
2.https://www.cnblogs.com/swordfall/p/10734403.html
3.http://www.ityouknow.com/jvm/2017/08/29/GC-garbage-collection.html
4.https://www.jianshu.com/p/aeac68236603
5.https://blog.csdn.net/u010798968/article/details/72867690
6.https://blog.csdn.net/qq_27093465/article/details/107044384

你可能感兴趣的:(jvm,jvm,垃圾回收,java)