成都私人网站制作,抖音特效开放平台官网,最新seo快排技术qq,the7企业中 英文wordpress模板转载自 深入浅出 Java CMS 学习笔记 引子
带着问题去学习一个东西#xff0c;才会有目标感#xff0c;我先把一直以来自己对CMS的一些疑惑罗列了下#xff0c;希望这篇学习笔记能解决掉这些疑惑#xff0c;希望也能对你有所帮助。
1、 CMS出现的初衷、背景和目的#x…转载自 深入浅出 Java CMS 学习笔记 引子
带着问题去学习一个东西才会有目标感我先把一直以来自己对CMS的一些疑惑罗列了下希望这篇学习笔记能解决掉这些疑惑希望也能对你有所帮助。
1、 CMS出现的初衷、背景和目的
2、 CMS的适用场景
3、 CMS的trade-off是什么?优势、劣势和代价
4、 CMS会回收哪个区域的对象
5、 CMS的GC Roots包括那些对象
6、 CMS的过程
7、 CMS和Full gc是不是一回事
8、 CMS何时触发
9、 CMS的日志如何分析
10、 CMS的调优如何做
11、 CMS扫描那些对象
12、 CMS和CMS collector的区别
13、 CMS的推荐参数设置
14、 为什么ParNew可以和CMS配合使用而Parallel Scanvenge不可以
一、基础知识
1、 CMS收集器Mostly-Concurrent收集器也称并发标记清除收集器Concurrent Mark-Sweep GCCMS收集器它管理新生代的方式与Parallel收集器和Serial收集器相同而在老年代则是尽可能得并发执行每个垃圾收集器周期只有2次短停顿。
2、 我之前对CMS的理解以为它是针对老年代的收集器。今天查阅了《Java性能优化权威指南》和《Java性能权威指南》两本书确认之前的理解是错误的。
3、 CMS的初衷和目的为了消除Throught收集器和Serial收集器在Full GC周期中的长时间停顿。
4、 CMS的适用场景如果你的应用需要更快的响应不希望有长时间的停顿同时你的CPU资源也比较丰富就适合适用CMS收集器。
二、CMS的过程
CMS的正常过程
这里我们首先看下CMS并发收集周期正常完成的几个状态。
1、STW初始标记这个阶段是标记从GcRoots直接可达的老年代对象、新生代引用的老年代对象就是下图中灰色的点。这个过程是单线程的JDK7之前单线程JDK8之后并行可以通过参数CMSParallelInitialMarkEnabled调整。 初始标记标记的对象
2、并发标记由上一个阶段标记过的对象开始tracing过程标记所有可达的对象这个阶段垃圾回收线程和应用线程同时运行如上图中的灰色的点。在并发标记过程中应用线程还在跑因此会导致有些对象会从新生代晋升到老年代、有些老年代的对象引用会被改变、有些对象会直接分配到老年代这些受到影响的老年代对象所在的card会被标记为dirty用于重新标记阶段扫描。这个阶段过程中老年代对象的card被标记为dirty的可能原因就是下图中绿色的线 并发标记过程中受到影响的对象
3、预清理预清理也是用于标记老年代存活的对象目的是为了让重新标记阶段的STW尽可能短。这个阶段的目标是在并发标记阶段被应用线程影响到的老年代对象包括1老年代中card为dirty的对象2幸存区(from和to)中引用的老年代对象。因此这个阶段也需要扫描新生代老年代。【PS会不会扫描Eden区的对象我看源代码猜测是没有还需要继续求证】 预清理中扫描from和to区
4、可中断的预清理这个阶段的目标跟“预清理”阶段相同也是为了减轻重新标记阶段的工作量。可中断预清理的价值在进入重新标记阶段之前尽量等到一个Minor GC尽量缩短重新标记阶段的停顿时间。另外可中断预清理会在Eden达到50%的时候开始这时候离下一次minor gc还有半程的时间这个还有另一个意义即避免短时间内连着的两个停顿如下图资料所示 避免连续停顿的发生
在预清理步骤后如果满足下面两个条件就不会开启可中断的预清理直接进入重新标记阶段 Eden的使用空间大于“CMSScheduleRemarkEdenSizeThreshold”这个参数的默认值是2M Eden的使用率大于等于“CMSScheduleRemarkEdenPenetration”这个参数的默认值是50%。
如果不满足上面两个条件则进入可中断的预清理可中断预清理可能会执行多次那么退出这个阶段的出口有两个源码参见下图 设置了CMSMaxAbortablePrecleanLoops并且执行的次数超过了这个值这个参数的默认值是0 CMSMaxAbortablePrecleanTime执行可中断预清理的时间超过了这个值这个参数的默认值是5000毫秒。 可中断预清理退出的条件 如果是因为这个原因退出gc日志打印如下
可中断预清理由于时间退出
有可能可中断预清理过程中一直没等到Minor gc这时候进入重新标记阶段的话新生代还有很多活着的对象就回导致STW变长因此CMS还提供了CMSScavengeBeforeRemark参数可以在进入重新标记之前强制进行依次Minor gc。
5、 STW重新标记重新扫描堆中的对象进行可达性分析,标记活着的对象。这个阶段扫描的目标是新生代的对象 Gc Roots 前面被标记为dirty的card对应的老年代对象。如果预清理的工作没做好这一步扫描新生代的时候就会花很多时间导致这个阶段的停顿时间过长。这个过程是多线程的。
6、并发清除用户线程被重新激活同时将那些未被标记为存活的对象标记为不可达
7、 并发重置CMS内部重置回收器状态准备进入下一个并发回收周期。
CMS的异常情况
上面描述的是CMS的并发周期正常完成的情况但是还有几种CMS并发周期失败的情况
1、并发模式失败Concurrent mode failureCMS的目标就是在回收老年代对象的时候不要停止全部应用线程在并发周期执行期间用户的线程依然在运行如果这时候如果应用线程向老年代请求分配的空间超过预留的空间担保失败就回触发concurrent mode failure然后CMS的并发周期就会被一次Full GC代替——停止全部应用进行垃圾收集并进行空间压缩。如果我们设置了UseCMSInitiatingOccupancyOnly和CMSInitiatingOccupancyFraction参数其中CMSInitiatingOccupancyFraction的值是70那预留空间就是老年代的30%。
2、 晋升失败新生代做minor gc的时候需要CMS的担保机制确认老年代是否有足够的空间容纳要晋升的对象担保机制发现不够则报concurrent mode failure如果担保机制判断是够的但是实际上由于碎片问题导致无法分配就会报晋升失败。
3、 永久代空间或Java8的元空间耗尽默认情况下,CMS不会对永久代进行收集一旦永久代空间耗尽就回触发Full GC。
三、CMS的调优
1、 针对停顿时间过长的调优
首先需要判断是哪个阶段的停顿导致的然后再针对具体的原因进行调优。使用CMS收集器的JVM可能引发停顿的情况有
1Minor gc的停顿
2并发周期里初始标记的停顿
3并发周期里重新标记的停顿
4Serial-Old收集老年代的停顿
5Full GC的停顿。其中并发模式失败会导致第4种情况晋升失败和永久代空间耗尽会导致第5种情况。
2、 针对并发模式失败的调优 想办法增大老年代的空间增加整个堆的大小或者减少年轻代的大小 以更高的频率执行后台的回收线程即提高CMS并发周期发生的频率。设置UseCMSInitiatingOccupancyOnly和CMSInitiatingOccupancyFraction参数调低CMSInitiatingOccupancyFraction的值但是也不能调得太低太低了会导致过多的无效的并发周期会导致消耗CPU时间和更多的无效的停顿。通常来讲这个过程需要几个迭代但是还是有一定的套路参见《Java性能权威指南》中给出的建议摘抄如下 “ 对特定的应用程序该标志的更优值可以根据 GC 日志中 CMS 周期首次启动失败时的值得到。具体方法是在垃圾回收日志中寻找并发模式失效找到后再反向查找 CMS 周期最近的启动记录然后根据日志来计算这时候的老年代空间占用值然后设置一个比该值更小的值。” 增多回收线程的个数 CMS默认的垃圾收集线程数是*CPU个数 3/4*这个公式的含义是当CPU个数大于4个的时候垃圾回收后台线程至少占用25%的CPU资源。举个例子如果CPU核数是1-4个那么会有1个CPU用于垃圾收集如果CPU核数是5-8个那么久会有2个CPU用于垃圾收集。
3、 针对永久代的调优
如果永久代需要垃圾回收或元空间扩容就会触发Full GC。默认情况下CMS不会处理永久代中的垃圾可以通过开启CMSPermGenSweepingEnabled配置来开启永久代中的垃圾回收开启后会有一组后台线程针对永久代做收集需要注意的是触发永久代进行垃圾收集的指标跟触发老年代进行垃圾收集的指标是独立的老年代的阈值可以通过CMSInitiatingPermOccupancyFraction参数设置这个参数的默认值是80%。开启对永久代的垃圾收集只是其中的一步还需要开启另一个参数——CMSClassUnloadingEnabled使得在垃圾收集的时候可以卸载不用的类。
四、CMS的trade-off是什么
1、 优势 低延迟的收集器几乎没有长时间的停顿应用程序只在Minor gc以及后台线程扫描老年代的时候发生极其短暂的停顿。
2、 劣势 更高的CPU使用必须有足够的CPU资源用于运行后台的垃圾收集线程在应用程序线程运行的同时扫描堆的使用情况。【PS现在服务器的CPU资源基本不是问题这个点可以忽略】 CMS收集器对老年代收集的时候不再进行任何压缩和整理的工作意味着老年代随着应用的运行会变得碎片化碎片过多会影响大对象的分配虽然老年代还有很大的剩余空间但是没有连续的空间来分配大对象这时候就会触发Full GC。CMS提供了两个参数来解决这个问题1UseCMSCompactAtFullCollection在要进行Full GC的时候进行内存碎片整理2CMSFullGCsBeforeCompaction每隔多少次不压缩的Full GC后执行一次带压缩的Full GC。 会出现浮动垃圾在并发清理阶段用户线程仍然在运行必须预留出空间给用户线程使用因此CMS比其他回收器需要更大的堆空间。
五、几个问题的解答
1、为什么ParNew可以和CMS配合使用而Parallel Scanvenge不可以
答这个跟Hotspot VM的历史有关Parallel Scanvenge是不在“分代框架”下开发的而ParNew、CMS都是在分代框架下开发的。
2、CMS中minor gc和major gc是顺序发生的吗
答不是的可以交叉发生即在并发周期执行过程中是可以发生Minor gc的这个找个gc日志就可以观察到。
3、CMS的并发收集周期合适触发
由下图可以看出CMS 并发周期触发的条件有两个 触发cms并发周期的条件 阈值检查机制老年代的使用空间达到某个阈值JVM的默认值是92%jdk1.5之前是68%jdk1.6之后是92%或者可以通过CMSInitiatingOccupancyFraction和UseCMSInitiatingOccupancyOnly两个参数来设置这个参数的设置需要看应用场景设置得太小会导致CMS频繁发生设置得太大会导致过多的并发模式失败。例如 动态检查机制JVM会根据最近的回收历史估算下一次老年代被耗尽的时间快到这个时间的时候就启动一个并发周期。设置UseCMSInitiatingOccupancyOnly这个参数可以将这个特性关闭。
4、CMS的并发收集周期会扫描哪些对象会回收哪些对象
答CMS的并发周期只会回收老年代的对象但是在标记老年代的存活对象时可能有些对象会被年轻代的对象引用因此需要扫描整个堆的对象。
5、CMS的gc roots包括哪些对象
答首先在JVM垃圾收集中Gc Roots的概念如何理解(参见R大对GC roots的概念的解释)第二CMS的并发收集周期中如何判断老年代的对象是活着我们前面提到了在CMS的并发周期中仅仅扫描Gc Roots直达的对象会有遗漏还需要扫描新生代的对象。如下图中的蓝色字体所示CMS中的年轻代和老年代是分别收集的因此在判断年轻代的对象存活的时候需要把老年代当作自己的GcRoots这时候并不需要扫描老年代的全部对象而是使用了card table数据结构如果一个老年代对象引用了年轻代的对象则card中的值会被设置为特殊的数值反过来判断老年代对象存活的时候也需要把年轻代当作自己的Gc Roots这个过程我们在第三节已经论述过了。 老年代和新生代互相作为Gc Roots
6、如果我的应用决定使用CMS收集器推荐的JVM参数是什么
答我自己的应用使用的参数如下是根据PerfMa的xxfox生成的大家也可以使用这个产品调优自己的JVM参数
-Xmx4096M -Xms4096M -Xmn1536M -XX:MaxMetaspaceSize512M -XX:MetaspaceSize512M -XX:UseConcMarkSweepGC -XX:UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction70 -XX:ExplicitGCInvokesConcurrentAndUnloadsClasses -XX:CMSClassUnloadingEnabled -XX:ParallelRefProcEnabled -XX:CMSScavengeBeforeRemark -XX:ErrorFile/home/admin/logs/xelephant/hs_err_pid%p.log -Xloggc:/home/admin/logs/xelephant/gc.log -XX:HeapDumpPath/home/admin/logs/xelephant -XX:PrintGCDetails -XX:PrintGCDateStamps -XX:HeapDumpOnOutOfMemoryError
7、CMS相关的参数总结需要注意的是这里我没有考虑太多JDK版本的问题JDK1.7和JDK1.8这些参数的配置有些默认值可能不一样具体使用的时候还需要根据具体的版本来确认怎么设置 六、读者讨论
1、关于CMS收集器的回收范围下面这张图是有误导的从官方文档上看来CMS收集器包括年轻代和老年代的收集只不过对年轻代的收集的策略和ParNew相同。 2、concurrent mode failure和promotion failed触发的Full GC有啥不同(这个问题是我、阿飞、蒋晓峰一起讨论的结果)
答concurrent mode failure触发的Full GC不是我们常说的Full GC——正常的Full GC其实是整个gc过程包括ygc和cms gc。也就是说这个问题本身是有问题的concurrent mode failure的时候触发的并不是我们常说的Full GC。然后再去讨论一个遗漏的知识点CMS gc的并发周期有两种模式foreground和background。 concurrent mode failure触发的是foreground模式会暂停整个应用会将一些并行的阶段省掉做一次老年代收集行为跟Serial-Old的一样至于在这个过程中是否需要压缩则需要看三个条件 ① 我们设置了UseCMSCompactAtFullCollection和CMSFullGCsBeforeCompaction前者设置为true后者默认是0前者表示是在Full GC的时候执行压缩后者表示是每隔多少个进行压缩默认是0的话就是每次Full GC都压缩 ② 用户调用了System.gc()而且DisableExplicitGC没有开启 ③ young gen报告接下来如果做增量收集会失败。 promotion failed触发的是我们常说的的Full GC对年轻代和老年代都会回收并进行整理。
3、promotion failed和concurrent mode failure的触发原因有啥不同 promotion failed是说担保机制确定老年代是否有足够的空间容纳新来的对象如果担保机制说有但是真正分配的时候发现由于碎片导致找不到连续的空间而失败 concurrent mode failure是指并发周期还没执行完用户线程就来请求比预留空间更大的空间了即后台线程的收集没有赶上应用线程的分配速度。
4、什么情况下才选择使用CMS收集器呢
答我之前的观念是小于8G的都用CMS大于8G的选择G1。蒋晓峰跟我讨论了下这个观念提出了一些别的想法我觉得也有道理记录在这里 除了看吞吐量和延时还需要看具体的应用比方说ESLucene和G1是不兼容的因此默认的收集器就是CMS。 小于3G的堆如果不是对延迟有特别高的需求不建议使用CMS主要是由于CMS的几个缺点导致的1并发周期的触发比例不好设置2抢占CPU时间3担保判断导致YGC变慢4碎片问题。 -END-