# 学习背景 - 关于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必备的三个参数 ```java -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 ```java 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执行期间用户线程申请的内存空间**,**这个错误可能触发两种情况** - **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这两个参数** 4. 所以通常来说不建议设置上面两个参数,否则可能在Java8中会触发**foreground collector**,**可能会更慢(单线程)**。**所以通常当出现`concurrent mode failure`时触发的都是Serial Old GC** 1. **关于UseCMSCompactAtFullCollection和CMSFullGCsBeforeCompaction的警告源代码** ##### runtime\arguments.cpp ```c 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."); } ``` 2. 关于用哪种处理方式的源代码 gc_implementation/concurrentMarkSweep/concurrentMarkSweepGeneration.cpp ```c 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-sweep do_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的阈值是不断变化的** ```` jstat -gccause 23270 1000 S0 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点 ```shell jstat -gccause pid >> cms.log jmap -histo pid >> cms.log jstat -gccause pid >> cms.log jmap -histo:live pid >> cms.log ``` ## metaspace > **设置 -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m,注意如果设置的过小,则会引起fgc甚至metaspace oom**
微信关注我们
原文链接:https://blog.51cto.com/alex4dream/2946748
转载内容版权归作者及来源网站所有!
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
相关文章
发表评论
资源下载
更多资源优质分享Android(本站安卓app)
近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。
Mario,低调大师唯一一个Java游戏作品
马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。
Apache Tomcat7、8、9(Java Web服务器)
Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成。因为Tomcat 技术先进、性能稳定,而且免费,因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web 应用服务器。
Sublime Text 一个代码编辑器
Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。