【JVM】垃圾收集器-Serial、Parallel、ParNew、CMS的特性与优缺点总结

  • Serial垃圾收集器
  • Parallel Scavenge垃圾收集器
  • ParNew垃圾收集器
  • CMS垃圾收集器
  • Rset与CardTable
  • 打印GC日志到文件
  • 总结

Serial垃圾收集器

  • Serial垃圾收集器是一个历史非常悠久的垃圾收集器,JDK1.3.1前是HotSpot年轻代垃圾收集的唯一选择;
  • 年轻代采用复制算法,老年代采用标记-整理算法
  • Serial Old垃圾收集器是Serial的老年代版本,它同样是一个单线程收集器;它主要有两大用途:一种用途是在JDK1.5及以前的版本中与Parallel Scavenge垃圾收集器搭配使用,另一种用途是作为CMS垃圾收集器的备选方案(在并发失败后使用,但是尽量避免,性能很低);
  • 开启Serial垃圾收集器的JVM参数:-XX:+UseSerialGC,-XX:+UseSerialOldGC;

缺点

  • Serial垃圾收集器是单线程收集,并且在垃圾收集阶段需要STW,因此在多核CPU与多线程当道的今天,Serial垃圾收集器的垃圾回收效率会相对低下;

优点

  • 实现比较简单,并且在单线程的物理机运行环境下,由于没有多线程交互带来的开销,其单线程收集效率会比较高;

2020-07-11_17-35-42.png

Parallel Scavenge垃圾收集器

  • Parallel Scavenge垃圾收集器可以认为是Serial收集器的多线程版本(升级版本),因为除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和Serial收集器类似;
  • Parallel垃圾收集器默认的收集线程数跟cpu核数相同(逻辑核数),当然也可以用参数(XX:ParallelGCThreads)指定垃圾收集开启的线程数,但是一般不推荐修改;
  • Parallel Scavenge垃圾收集器的目标是高吞吐量,即减少垃圾收集时间(高效的利用CPU),让用户代码获得更长的运行时间;
  • 年轻代采用复制算法,老年代采用标记-整理算法
  • Parallel Old垃圾收集器是Parallel Scavenge垃圾收集器的老年代版本
  • JDK8默认使用Parallel Scavenge垃圾收集器和Parallel Old垃圾收集器
  • 开启Parallel Scavenge垃圾收集器的JVM参数:-XX:+UseParallelGC,-XX:+UseParallelOldGC;

2020-07-11_17-43-43.png

ParNew垃圾收集器

  • ParNew垃圾收集器其实跟Parallel垃圾收集器很类似,区别主要在于它可以和CMS收集器配合使用;(Parallel不可以与CMS搭配使用)
  • 年轻代采用复制算法,老年代采用标记-整理算法
  • ParNew与CMS搭配使用(ParNew回收年轻代,CMS回收老年代)是在JDK1.8以及以前的版本中很长一段时间内企业普遍采用的垃圾收集解决方案
  • 开启ParNew垃圾收集器的JVM参数:-XX:+UseParNewGC;

CMS(Concurrent Mark Sweep)垃圾收集器

  • CMS垃圾收集器是HotSpot虚拟机第一款真正意义上的并发垃圾收集器(垃圾收集线程与用户线程并发执行),虽然还是会有STW,但是其耗时非常短,是一款以用户体验至上的垃圾收集器;
  • CMS垃圾收集器的CMS GC并不等同于Full GC,虽然CMS GC是操作的老年代,但是其STW时间才会被真正计入Full GC的时间,这个时间是非常短的;
  • CMS垃圾收集器实现也比较复杂,其垃圾收集可以分为如下几个阶段:
  • ①初始标记:暂停所有的用户线程(STW),并记录下GC Roots直接(只标记根节点)能引用的对象,速度很快;
  • ②并发标记:并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程, 这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行。因为用户程序并发运行,可能会导致已经标记过的对象的状态发生改变;
  • ③重新标记:重新标记阶段就是为了修正并发标记期间因为用户程序并发运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。主要用到三色标记里的增量更新算法做重新标记;
  • ④并发清理:开启用户线程,同时GC线程开始对未标记的区域做清扫。这个阶段如果有新增对象会被标记为黑色(三色标记算法)不做任何处理;
  • ⑤并发重置:重置本次GC过程中的标记数据;

2020-07-11_20-30-14.png

优点

  • 适用内存需求特别大的程序运行;
  • 垃圾收集过程中STW的时间非常短,用户体验比较好;

缺点

  • 对CPU资源敏感,对于核心数少的服务器CPU资源占用会非常大,逻辑核心数大于4个后才会改善很多(与用户线程抢占硬件资源);
  • 无法处理浮动垃圾(在并发标记和并发清理阶段又新产生的垃圾,这种浮动垃圾只能等到下一次gc再清理);
  • CMS垃圾收集器使用的垃圾回收算法标记-清理算法会产生大量的内存碎片,当然这可以通过JVM参数打开内存整理功能来解决这个问题;
  • 由于CMS垃圾收集器在并发标记与并发清理阶段与用户线程并发执行,因此可能在并发执行过程中,可能会有新对象被提升到老年代(进入老年代的一些策略),从而引发内存空间不足,导致并发收集失败(concurrent mode failure),此时会立即STW,并用serial old垃圾收集器来回收垃圾,等回收完后再开启用户线程;而serial old垃圾收集器使用单线程,在大内存下执行会非常慢,对程序性能会造成很大的影响;

CMS常见的JVM参数设置

  • -XX:+UseConcMarkSweepGC:开启CMS垃圾收集器;
  • -XX:ConcGCThreads:并发GC线程数;
  • -XX:+UseCMSCompactAtFullCollection:开启CMS GC之后做压缩整理(减少内存碎片);
  • -XX:CMSFullGCsBeforeCompaction:多少次CMS GC后做一个压缩整理(默认:0,代表每次都会做),整理是会消耗系统资源的,如果系统压力只集中在一段时间的话,每次整理也没关系,但是如果系统压力持续很大的话,就不建议每次都做整理了;
  • -XX:CMSInitiatingOccupancyFraction:当老年代使用比例达到多少时会触发CMS GC(单位:百分比,默认92);实际使用中需要仔细评估这个参数的值(比如,大对象比较多就百分比往下调),为了预防并发收集失败的问题;
  • -XX:+UseCMSInitiatingOccupancyOnly:上条参数值(-XX:CMSInitiatingOccupancyFraction)是否允许JVM动态调整(如配置了则不动态调整);
  • -XX:+CMSScavengeBeforeRemark:在CMS GC(针对老年代)前是否进行一次Minor GC;
  • -XX:+CMSParallellnitialMarkEnable:在初始标记时开启多线程执行;
  • -XX:+CMSParallelRemarkEnabled:在重新标记时开启多线程执行;

Rset(Remember Set)与CardTable

  • 当然在这些涉及部分区域的垃圾收集器而言,其都会面临一个比较大的问题,就是跨代引用的问题;比如,在老年代的对象引用年轻代的对象的情况,这时候如果一个年轻代垃圾收集器还要去扫描老年代的GC Root显然不合适,而且性能开销也比较大;Rset(记录从非收集区到收集区的指针集合)就是用来解决这个问题的;
  • 垃圾收集场景中,收集器只需通过Rset判断出某一块非收集区域是否存在指向收集区域的指针即可,无需了解跨代引用指针的全部细节,也无须扫描非收集区的对象;

HotSpot虚拟机对Rset的实现

  • hotSpot使用一种叫做“卡表”(cardtable)的方式实现Rset,也是目前最常用的一种方式;
  • 卡表是使用一个字节数组实现:"CARD_TABLE[]",每个元素对应着其标识的内存区域一块特定大小的内存块,称为“卡页”(HotSpot虚拟机指定一个卡页为512字节);
  • 一个卡页中可能有多个对象,当一个卡页中有一个元素存在跨代指针(通过元素赋值,写屏障检测跨代指针),那么这个卡页就标记为Dirty(元素值置为1);GC时,会将所有Dirty的卡页中的元素加入GC Root里;

打印GC日志到文件

  • 对于java应用我们可以通过一些配置把程序运行过程中的GC日志全部打印出来,然后分析GC日志得到关键性指标,分析GC原因,对JVM参数进行调优;
//指定GC文件输出路径
‐Xloggc:./gc‐%t.log
//开启打印GC日志
‐XX:+PrintGCDetails
//打印GC的日期与时间
‐XX:+PrintGCDateStamps  
‐XX:+PrintGCTimeStamps
//打印GC原因
‐XX:+PrintGCCause
//使用滚动日志打印,一共20个文件,每个文件100M
‐XX:+UseGCLogFileRotation 
‐XX:NumberOfGCLogFiles=10 
‐XX:GCLogFileSize=100M
  • 打印完的日志文件内容可以导入一些第三方的GC日志分析工具来进行分析,然后给出JVM优化建议,比如:GC Viewer,GCeasy;
  • GCeasy是一个在线的分析工具,分析的维度还比较多,值得推荐,地址是:https://gceasy.io/;

总结

  • 串行垃圾收集器的目标是让整个垃圾收集过程尽可能高效,时间尽可能短,而并发垃圾收集器目标是用户体验,让STW时间尽可能短,但是垃圾收集过程持续的时间是比较长的;
  • 如果不是单核处理器的服务器的话,现在已经不再单独使用Serial垃圾收集器了;
  • Parallel Scavenge垃圾收集器的目标是CPU的高吞吐量,在多线程垃圾收集时会STW(时间相对CMS长很多),适合一些计算量大并且内存需求不是特别大的(一般是小于8GB)的场景;至于STW的时间,我们可以通过调整垃圾收集器的各种参数使得垃圾回收次数和垃圾回收时STW时间尽可能小来提升用户体验。

注意:本文归作者所有,未经作者允许,不得转载

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
CMS(Concurrent Mark Sweep)是一种并发的垃圾回收器,它的主要优势是尽可能地减少应用程序的停顿时间。下面是CMS垃圾回收器的实现原理: 1. 初始标记(Initial Mark):这个阶段会暂停应用程序的线程,标记出所有的根对象,即 GC Roots,比如静态变量和活动线程等。这个阶段很快就完成了,但是应用程序会被短暂地暂停。 2. 并发标记(Concurrent Mark):在这个阶段,垃圾回收器会扫描堆中的对象,标记出所有活跃的对象,这个阶段与应用程序是并发执行的,所以不会停顿程序执行。因为这个阶段与应用程序并发执行,所以在这个阶段,堆中的对象可能会发生变化,因此需要在下一个阶段扫描时重新确认它们是否仍然是活跃的。 3. 重新标记(Remark):在这个阶段,垃圾回收器会重新遍历堆中的对象,标记出在并发标记阶段发生变化的对象。这个阶段需要暂停应用程序,但是它的时间通常比初始标记阶段更短。 4. 并发清除(Concurrent Sweep):在这个阶段,垃圾回收器会扫描堆中的对象,清除所有被标记为垃圾的对象。这个阶段与应用程序是并发执行的,因此不会停顿程序执行。由于在清除对象时不需要移动对象,因此这个阶段的性能通常比标记阶段更高。 需要注意的是,CMS垃圾回收器并不会移动对象,因此当堆中的对象达到一定的程度时,会出现碎片化的问题。为了解决这个问题,JVM还提供了G1(Garbage First)垃圾回收器。 另外,CMS垃圾回收器虽然能够减少应用程序的停顿时间,但是在执行过程中会产生一些额外的负载,因此需要根据应用程序的特点和需求来选择合适的垃圾回收器。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Maoway稻草人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值