热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【Java进阶营】「终」虚拟机研究系列面向CMS垃圾回收器的性能优化实战

学习背景关于CMSGC介绍和调优的文章比较多,但大多没有经过验证。因为CMS目前在Java9之前还是相对用的较多(G1也需要持续去调研)&
学习背景
  • 关于CMS GC介绍和调优的文章比较多,但大多没有经过验证。因为CMS目前在Java9之前还是相对用的较多(G1也需要持续去调研),所以这里把CMS的一些重要知识和调优经验总结一下。

  • 相关jvm源代码版本为/openjdk-8-src-b132-03_mar_2014/openjdk/hotspot/src/share/vm,个人建议还是选择openjdk7比较好,因为是行业标准!


除了OpenJDK的源代码和R大以外,什么都不要轻易相信。


CMS的一些重要知识点


使用CMS GC必备的三个参数

-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=n
-XX:+UseCMSInitiatingOccupancyOnly

  • -XX:CMSInitiatingOccupancyFraction=n:CMS回收器机制触发回收垃圾的百分比比重 jdk8的时候为92%。

  • -XX:+UseCMSInitiatingOccupancyOnly:是否每次都采用固化参数进行分配触发MajorGC


NewRatio参数参考


  • 默认的NewRatio为2,表示新生代和老年代比例是1:2,即占堆的1/3

但是实际设置了-Xmx和-Xms后,新生代的大小不符合预期


原因:runtime.arguments.cpp

else if (UseConcMarkSweepGC) {set_cms_and_parnew_gc_flags();
}
const size_t preferred_max_new_size_unaligned =MIN2(max_heap/(NewRatio+1), ScaleForWordSize(young_gen_per_worker * parallel_gc_threads));

cms新生代的大小是计算出来的

所以通常使用cms的时候,建议手动指定新生代大小参数

(-XX:NewRatio或者-Xmn或者-XX:NewSize/-XX:MaxNewSize)

另外JDK-6862534 : -XX:NewRatio completely ignored when combined with -XX:+UseConcMarkSweepGC,之前是即使手动指定 -XX:NewRatio,也无效,现早已修复

使用jstat -gccause pid 观察cms fgc的时候,发现每次到阈值回收的时候,fgc每次会跳2次


  • 因为cms的一个并发周期内有两个阶段initial mark与final re-mark,这两个阶段都是"stop the world"‘,不过暂停时间较短

  • 而jstat的这个fgc的计数器是说的应用暂停的次数,注意这里所指的是’cms gc’引起的stw,详细可参考jstat显示的full GC次数与CMS周期的关系

  • 如果观察cms fgc,突然发现stw的时间很长,多达几秒甚至更多,一定是出现了异常情况,而这些情况的代价都十分昂贵,在做cms调优的时候要尽可能的避免


concurrent mode failure


  1. cms并发周期执行期间,用户的线程依然在运行,如果这时候如果应用线程向老年代请求分配的空间超过预留的空间,就会抛出该错误 - 后台线程的收集没有赶上应用线程的分配速度

  2. 有时候“空间不足”是CMS GC时当前的浮动垃圾过多(内存碎片)导致暂时性的空间不足,而浮动垃圾就是cms执行期间用户线程申请的内存空间,这个错误可能触发两种情况在此我向大家推荐一个架构学习交流圈。交流学习指导伪鑫:1253431195(里面有大量的面试题及答案)里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

    • cms的foreground模式(默认的cms gc属于background模式),这个模式是CMS自己的mark-sweep不实现做并发的(串行的)old generation GC,不过会将一些阶段省略掉
    • CMS的foreground collector的算法就是普通的mark-sweep。它收集的范围只是CMS的old generation,而不包括其它generation(有争议哦,标记会涉及到young区以及同时也可以scavenge机制预先触发minorgc)。因而它在HotSpot VM里不叫做full GC

Serial Old GC


  • mark-sweep-compact算法
  • 它收集的范围是整个GC堆,包括Java heap的young generation和old generation,以及non-Java heap的permanent generation。因而其名 Full GC

前者的出现原因


A STW foreground collection can pick up where a concurrent background collection left off to try to avoid a full GC. This is nice but normally it has worse performance than a full GC

即是为了避免fgc,但是往往性能甚至比fgc更差。

对于第一种foreground模式,必须要 -XX:-UseCMSCompactAtFullCollection-XX:CMSFullGCsBeforeCompaction 设置大于0


  • 但是UseCMSCompactAtFullCollection默认为true,CMSFullGCsBeforeCompaction默认是0(每次fullgc都会进行压缩!),所以一定会触发第二种Serial Old GC

参考:

  • https://bugs.openjdk.java.net/browse/JDK-8010202
  • https://bugs.openjdk.java.net/browse/JDK-8064702
  • https://bugs.openjdk.java.net/browse/JDK-8027132

均建议foreground collector在Java8废弃,在Java9移除,包括UseCMSCompactAtFullCollection和CMSFullGCsBeforeCompaction这两个参数

  1. 所以通常来说不建议设置上面两个参数,否则可能在Java8中会触发foreground collector,可能会更慢(单线程)所以通常当出现concurrent mode failure时触发的都是Serial Old GC

  2. 关于UseCMSCompactAtFullCollection和CMSFullGCsBeforeCompaction的警告源代码


runtime\arguments.cpp

if (FLAG_IS_CMDLINE(UseCMSCompactAtFullCollection)) {warning("UseCMSCompactAtFullCollection is deprecated and will likely be removed in a future release.");}if (FLAG_IS_CMDLINE(CMSFullGCsBeforeCompaction)) {warning("CMSFullGCsBeforeCompaction is deprecated and will likely be removed in a future release.");}

  1. 关于用哪种处理方式的源代码 gc_implementation/concurrentMarkSweep/concurrentMarkSweepGeneration.cpp

void CMSCollector::acquire_control_and_collect{
...
bool should_compact = false;
decide_foreground_collection_type(clear_all_soft_refs,&should_compact, &should_start_over);
...if (should_compact) {
...
// 这个就是mark-sweep-compact 的 Full GC
do_compaction_work(clear_all_soft_refs);
...}else {// mark-sweepdo_mark_sweep_work(clear_all_soft_refs, first_state,should_start_over);
}*should_compact =UseCMSCompactAtFullCollection &&((_full_gcs_since_conc_gc >= CMSFullGCsBeforeCompaction) ||GCCause::is_user_requested_gc(gch->gc_cause()) ||gch->incremental_collection_will_fail(true /* consult_young */));

而should_compact主要的一个判断逻辑就是判断UseCMSCompactAtFullCollection和CMSFullGCsBeforeCompaction这两个参数


concurrent promotion failed

Java Performance,The Definitive Guide的原文是这样描述的:

原文:


Here, CMS started a young collection and assumed that there was enough free space to hold all the promoted objects (otherwise, it would have declared a concurrent mode failure). That assumption proved incorrect: CMS couldn’t promote the objects because the old generation was fragmented (or, much less likely, because the amount of memory to be promoted was bigger than CMS expected).


翻译:


新生代垃圾收集,判断老年代似乎有足够的空闲空间可以容纳所有的晋升对象(否则,CMS收集器会报concurrent mode failure)。这个假设最终被证明是错误的,由于老年代空间的碎片化(或者,不太贴切的说,由于晋升实际要占用的内存超过了CMS收集器的判断),CMS收集器无法晋升这些对象




原文:


Sometimes we see these promotion failures even when thelogs show that there is enough free space in tenured generation. The reason is’fragmentation’ - the free space available in tenured generation is notcontiguous, and promotions from young generation require a contiguous freeblock to be available in tenured generation. CMS collector is a non-compactingcollector, so can cause fragmentation of space for some type of applications.


翻译:


  • CMS收集器对老年代收集的时候,不再进行任何压缩和整理的工作,意味着老年代随着应用的运行会变得碎片化;碎片过多会影响大对象的分配,虽然老年代还有很大的剩余空间,但是没有连续的空间来分配大对象

  • 如果在ParNew准备收集时CMS说晋升没问题,但ParNew已经开始收集之后确实遇到了晋升失败的情况。

  • promotion failed是说,担保机制确定老年代是否有足够的空间容纳新来的对象,如果担保机制说有,但是真正分配的时候发现由于碎片导致找不到连续的空间而失败;而concurrent mode failure是指并发周期还没执行完,用户线程就来请求比预留空间更大的空间了,即后台线程的收集没有赶上应用线程的分配速度

  • promotion failed触发fgc,触发模式同上,通常也是Serial Old GC


permgen (or the metaspace) fills up


  1. 对于Java8来说,这个主要是在metaspace扩容时触发的
  2. 如果老年代设置了CMS,则 Metasapce 扩容引起的FGC会转变成一次 CMS
  3. Java8中收集器默认就会收集元空间中不再载入的类

在刚启动应用后,通过jstat -gccause pid后看到出现了fgc,此时ou也没有占用

  • 通常这种情况是上面提到的metaspace扩容引起的,从LGCC也可以看到Metadata GC Threshold,触发的原因是因为Metaspace大小达到了GC阈值

  • MetaspaceSize主要是控制metaspaceGC发生的初始阈值,也是最小阈值,但是触发metaspaceGC的阈值是不断变化的在此我向大家推荐一个架构学习交流圈。交流学习指导伪鑫:1253431195(里面有大量的面试题及答案)里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

jstat -gccause 23270 1000S0 S1 E O M CCS YGC YGCT FGC FGCT GCT LGCC GCC 0.00 25.87 82.46 0.00 97.47 94.80 1 0.124 2 0.096 0.220 Metadata GC Threshold No GC

通过观察gc日志,出现cms异常的几种情况

[ParNew (promotion failed): ... (concurrent mode failure):...

这种情况是先出现了promotion failed,然后准备触发fgc。

而此时cms这在执行并发收集,此时则执行打断逻辑,输出concurrent mode failure

具体源代码也是concurrentMarkSweepGeneration.cpp

if (first_state > Idling) {report_concurrent_mode_interruption();
}
[ParNew (promotion failed): ...

这种情况就是单纯出现了promotion failed,此时cms未执行并发收集
(concurrent mode failure): …

这种情况是单纯的cms正在执行并发收集,然后用户线程申请内存空间不足jvm有一个内存担保机制,是类似于判断’老年代最大的可用连续空间是否大于新生代所有对象的总和’。但通常描述promotion failed的时候是指担保机制够了, 才会发生。那么既然有最大可用连续空间,为什么还会failed with 5.0 because a single contiguous chunk of space is not required
for promotions,即在jdk5后,晋升不需要连续空间了
所以这里的担保是指’老年代是否有足够的空间容纳要晋升的对象’,而不是连续空间。那么出现fail,则是碎片问题

CMS优化方向


原则


cms的的优势就是低延迟,但是如果出现了长时间的stw,则对应用程序有很大的影响
如果出现了concurrent mode failure和promotion failed,代价都非常昂贵,我们调优应该尽量避免这些情况


针对concurrent mode failure的优化

发生该失败的主要原因是由于CMS不能以足够快的速度清理老年代空间

当老年代空间的占用达到某个阈值时,并发回收就开始了。一个CMS后台线程开始扫描老年代空间,寻找无用的垃圾对象时,竞争就开始了。CMS收集器必须在老年代剩余的空间用尽之前,完成老年代空间的扫描及回收工作。否则如果在正常速度的比赛中失效,就会发生该错误

在并发清理阶段,用户线程仍然在运行,必须预留出空间给用户线程使用,会产生’浮动垃圾‘

常规优化途径如下


  • 以更高的频率执行后台的回收线程,即提高CMS并发周期发生的频率

  • 主要是调低CMSInitiatingOccupancyFraction的值

  • 但是不能太低,太低会导致过于频繁的gc,会消耗更多的的cpu和停顿


需要先计算老年代常驻内存大小,如占用60%,那么这个阈值则可以设置为约70%,否则会比较频繁gc

可以考虑担保机制,只要老年代预留剩余空间大于年轻代大小,比如新生代和老年代的比例是1 : 4,即新生代占用老年代的25%,那么这个阈值可以设置为70,即老年代还预留出来30%的空间

注意如果浮动垃圾很多的话,也无法解决该问题,即cms并发回收期间,浮动垃圾越来越多,占用预留空间,多次的ygc的话,会有填满预留空间的可能,虽然概率较低

两个条件综合考虑,如果设置了阈值70,但是老年代常驻内存很大,甚至超过70,那么此时的建议要提高堆内存,增加老年代的大小或者减少新生代的大小

针对promotion failed的优化

这个是cms最为严重的’碎片问题‘,我们要尽量避免这个发生后引起的fgc

所以优化这个问题,也可以描述为’如何解决碎片问题’

常规优化途径如下

增大堆内存,增加老年代大小,但要注意不要超过32g(the HotSpot JVM uses a trick to compress object pointers when heaps are less than around 32 GB)

尽早执行cms gc,合理设置CMSInitiatingOccupancyFraction,会合并老生代中相邻的free空间,可分配给较大的对象

和上面一样,也可以做一个老年代预留空间大于年轻代

到了阈值后,就会触发cms gc,但还是和上面说的,会产生浮动垃圾 + 碎片,还是会出现

另外一个比较“挫”的办法,是在每天凌晨访问量低的时候,主动执行一下fgc,执行一下’碎片压缩’

如System.gc,但是要注意是否开启了 -XX:+ExplicitGCInvokesConcurrent,-XX:DisableExplictGC

所以建议办法是用**jmap -histo:live**

另外晋升还包括to space空间小,可以根据情况尝试提高Survivor

CMS实战参数

日志,主要是用来排查cms相关问题

基础参数:

-Xloggc:gc_%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps

可选调试参数:

-XX:+PrintGCApplicationStoppedTime # 打印停顿日志-XX:+PrintTenuringDistribution:打印升迁日志-XX:+PrintPromotionFailure # 打印分派失败-XX:+PrintHeapAtGC gc 进行打印-XX:PrintFLSStatistics=1 较少使用 主要用于统计计算

cms相关


  1. 物理机内存:16G
  2. 预估老年代常驻对象如Player 3000,一个Player平均2M,大约6G,所以老年代比如建议10G
  3. -Xms12G -Xmx12G
  4. 设置新生代2G,老年代10G
  5. 设置CMSInitiatingOccupancyFraction为70,则老年代剩余空间为3G,大于新生代大小
  6. 可选:-XX:+CMSScavengeBeforeRemark

简单算法:


-XX:NewRatio=4,即新生代和老年代1:4

然后设置CMSInitiatingOccupancyFraction为70,即老年代剩余空间稍大新生代
但要保证这个70基本上要大于老年代常驻内存,否则可能会频繁cms gc

另外建议增加脚本,尝试手动执行fgc,整理碎片

如每天凌晨3点

jstat -gccause pid >> cms.log
jmap -histo pid >> cms.logjstat -gccause pid >> cms.log
jmap -histo:live pid >> cms.log

metaspace


设置 -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m,注意如果设置的过小,则会引起fgc甚至metaspace oom


推荐阅读
  • 生产环境下JVM调优参数的设置实例
     正文前先来一波福利推荐: 福利一:百万年薪架构师视频,该视频可以学到很多东西,是本人花钱买的VIP课程,学习消化了一年,为了支持一下女朋友公众号也方便大家学习,共享给大家。福利二 ... [详细]
  • 深入理解Java虚拟机的并发编程与性能优化
    本文主要介绍了Java内存模型与线程的相关概念,探讨了并发编程在服务端应用中的重要性。同时,介绍了Java语言和虚拟机提供的工具,帮助开发人员处理并发方面的问题,提高程序的并发能力和性能优化。文章指出,充分利用计算机处理器的能力和协调线程之间的并发操作是提高服务端程序性能的关键。 ... [详细]
  • 一次上线事故,30岁+的程序员踩坑经验之谈
    本文主要介绍了一位30岁+的程序员在一次上线事故中踩坑的经验之谈。文章提到了在双十一活动期间,作为一个在线医疗项目,他们进行了优惠折扣活动的升级改造。然而,在上线前的最后一天,由于大量数据请求,导致部分接口出现问题。作者通过部署两台opentsdb来解决问题,但读数据的opentsdb仍然经常假死。作者只能查询最近24小时的数据。这次事故给他带来了很多教训和经验。 ... [详细]
  • go channel 缓冲区最大限制_Golang学习笔记之并发.协程(Goroutine)、信道(Channel)
    原文作者:学生黄哲来源:简书Go是并发语言,而不是并行语言。一、并发和并行的区别•并发(concurrency)是指一次处理大量事情的能力 ... [详细]
  • 2018年人工智能大数据的爆发,学Java还是Python?
    本文介绍了2018年人工智能大数据的爆发以及学习Java和Python的相关知识。在人工智能和大数据时代,Java和Python这两门编程语言都很优秀且火爆。选择学习哪门语言要根据个人兴趣爱好来决定。Python是一门拥有简洁语法的高级编程语言,容易上手。其特色之一是强制使用空白符作为语句缩进,使得新手可以快速上手。目前,Python在人工智能领域有着广泛的应用。如果对Java、Python或大数据感兴趣,欢迎加入qq群458345782。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • 统一知识图谱学习和建议:更好地理解用户偏好
    本文介绍了一种将知识图谱纳入推荐系统的方法,以提高推荐的准确性和可解释性。与现有方法不同的是,本方法考虑了知识图谱的不完整性,并在知识图谱中传输关系信息,以更好地理解用户的偏好。通过大量实验,验证了本方法在推荐任务和知识图谱完成任务上的优势。 ... [详细]
  • SpringBoot整合SpringSecurity+JWT实现单点登录
    SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社 ... [详细]
  • 本文介绍了使用Spark实现低配版高斯朴素贝叶斯模型的原因和原理。随着数据量的增大,单机上运行高斯朴素贝叶斯模型会变得很慢,因此考虑使用Spark来加速运行。然而,Spark的MLlib并没有实现高斯朴素贝叶斯模型,因此需要自己动手实现。文章还介绍了朴素贝叶斯的原理和公式,并对具有多个特征和类别的模型进行了讨论。最后,作者总结了实现低配版高斯朴素贝叶斯模型的步骤。 ... [详细]
  • ShiftLeft:将静态防护与运行时防护结合的持续性安全防护解决方案
    ShiftLeft公司是一家致力于将应用的静态防护和运行时防护与应用开发自动化工作流相结合以提升软件开发生命周期中的安全性的公司。传统的安全防护方式存在误报率高、人工成本高、耗时长等问题,而ShiftLeft提供的持续性安全防护解决方案能够解决这些问题。通过将下一代静态代码分析与应用开发自动化工作流中涉及的安全工具相结合,ShiftLeft帮助企业实现DevSecOps的安全部分,提供高效、准确的安全能力。 ... [详细]
  • xhci规范中整体软件模型如下图所示:这里描述的主要是上图中xhci部分,包括软件和硬件。1.接口架构这里主要分为三部分:1)HostConfigurationSpace.每个xH ... [详细]
  • 基于分布式锁的防止重复请求解决方案
    一、前言关于重复请求,指的是我们服务端接收到很短的时间内的多个相同内容的重复请求。而这样的重复请求如果是幂等的(每次请求的结果都相同,如查 ... [详细]
author-avatar
孤独游侠1976_127
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有