JVM中的STW和CMS

 

Java中Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其余全部线程都被挂起(除了垃圾收集帮助器以外)。Java中一种全局暂停现象,全局停顿,全部Java代码中止,native代码能够执行,但不能与JVM交互;这些现象多半是因为gc引发。html

GC时的Stop the World(STW)是你们最大的敌人。但可能不少人还不清楚,除了GC,JVM下还会发生停顿现象。java

JVM里有一条特殊的线程--VM Threads,专门用来执行一些特殊的VM Operation,好比分派GC,thread dump等,这些任务,都须要整个Heap,以及全部线程的状态是静止的,一致的才能进行。因此JVM引入了安全点(Safe Point)的概念,想办法在须要进行VM Operation时,通知全部的线程进入一个静止的安全点。算法

除了GC,其余触发安全点的VM Operation包括:安全

1. JIT相关,好比Code deoptimization, Flushing code cache ;多线程

2. Class redefinition (e.g. javaagent,AOP代码植入的产生的instrumentation) ;并发

3. Biased lock revocation 取消偏向锁 ;app

4. Various debug operation (e.g. thread dump or deadlock check);jvm

监控安全点看看JVM到底发生了什么?高并发

最简单的作法,在JVM启动参数的GC参数里,多加一句:工具

-XX:+PrintGCApplicationStoppedTime

它就会把所有的JVM停顿时间(不仅是GC),打印在GC日志里。

2016-08-22T00:19:49.559+0800: 219.140: Total time for which application threads were stopped: 0.0053630 seconds

这是个颇有用的必配参数,能够打出几乎一切的停顿……

可是,在JDK1.7.40之前的版本,它竟然没有打印时间戳,因此只能知道JVM停了多久,但不知道何时停的。此时一个土办法就是加多一句“ -XX:+PrintGCApplicationConcurrentTime”,打印JVM在两次停顿之间的正常运行时间(一样没有时间戳),但好歹能配合有时间戳的GC日志,反推出Stop发生的时间了。

2016-08-22T00:19:50.183+0800: 219.764: Application time: 5.6240430 seconds

如何打印出事哪一种缘由致使的停顿呢?

再多加两个参数:-XX:+PrintSafepointStatistics -XX: PrintSafepointStatisticsCount=1

此时,在stdout中会打出相似的内容

vmop [threads: total initially_running wait_to_block]1913.425: GenCollectForAllocation [ 55 2 0 ] [time: spin block sync cleanup vmop] page_trap_count[ 0 0 0 0 6 ] 0

此日志分两段,第一段是时间戳,VM Operation的类型,以及线程概况

total: 安全点里的总线程数 

initially_running: 安全点时开始时正在运行状态的线程数 

wait_to_block: 在VM Operation开始前须要等待其暂停的线程数

第二行是到达安全点时的各个阶段以及执行操做所花的时间,其中最重要的是vmop

spin: 等待线程响应

safepoint号召的时间 

block: 暂停全部线程所用的时间 

sync: 等于 spin+block,这是从开始到进入安全点所耗的时间,可用于判断进入安全点耗时 

cleanup: 清理所用时间 

vmop: 真正执行VM Operation的时间

可见,那些不少但又很短的安全点,全都是RevokeBias,详见 偏向锁实现原理, 高并发的应用通常会干脆在启动参数里加一句"-XX:-UseBiasedLocking"取消掉它。另外还看到有些类型是no vm operation, 文档上说是保证每秒都有一次进入安全点(若是这秒已经GC过就不用了),给一些须要在安全点里进行,又非紧急的操做使用,好比一些采样型的Profiler工具,可用-DGuaranteedSafepointInterval来调整,不过实际看它并非每秒都会发生,时间不定。

在实战中,咱们利用安全点日志,发现过有程序定时调用Thread Dump等等状况。不过由于安全点日志默认输出到stdout,由于性能及stdout日志的整洁性等缘由,咱们平时默认没有开启它。只有在须要时才打开。

再再增长下面三个参数,能够知道更多VM里发生的事情。惋惜JVM不会由于设了这三个参数,就把安全点日志转移到vm.log里面来,而是白白打印了两次。

-XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=/dev/shm/vm.log

总结

本文关于快速理解Java垃圾回收和jvm中的stw的介绍就到这里,但愿对你们有所帮助,感兴趣的朋友能够参阅:浅谈Java回收对象的标记和对象的二次标记过程 、Java虚拟机装载和初始化一个class类代码解析 、Java中map遍历方式的选择问题详解等,有什么问题能够随时留言,小编会及时回复你们的。

 

 

 

并发标记清除(CMS)垃圾收集器

 

 

 

CMS GC设计之初最根本的目的就是减少最大响应时间。

随着愈来愈多的应用要求有一个垃圾收集器,它能比串行或并行垃圾收集器有更短的最坏状况的中断时间,牺牲一些应用的吞吐量来消除或极大地减小漫长的GC 中断数量也是可以接受的,针对这种状况,CMS 垃圾收集器被开发出来。

在CMS 垃圾收集器中,年轻代的垃圾收集与并行垃圾收集器很相似,它们是并行的并且会stop-the-world,也就是说在年轻代的垃圾收集过程当中全部的Java 应用线程都会被暂停,而垃圾收集工做会用多线程的方式来执行。须要注意的是,你能够给CMS 垃圾收集器配置一个单线程模式的年轻代收集器,但在Java 8 中并不推荐这个方式,这个选项在Java 9 中被移除了。

并行垃圾收集器与CMS 垃圾收集器最主要的区别是在老年代的收集上。CMS 收集器的老年代收集活动试图避免应用线程的长时间中断。为了实现这个目的,CMS 老年代收集器在应用线程执行的同时作了大部分工做(垃圾收集线程与应用线程同时工做),除了少许相对短的GC 同步暂停。一般来讲,绝大多数状况下CMS 是并发的,老年代收集的某些阶段会暂停应用线程,好比初始标记和从新标记阶段。在CMS 最初的实现中,初始标记和从新标记阶段都是单线程的,但如今它们都已经被改成多线程的。激活多线程的初始标记和从新标记阶段的HotSpot 命令行选项分别是-XX:+CMSParallelInitial Mark Enabled和-XX:CMSParallelRemarkEnabled,当经过命令行选项XX:+UseConcurrentMarkSweepGC 激活CMS 垃圾收集器时也会缺省自动激活这两个选项。

有可能,或者说极有可能会在一个老年代并发收集正在进行的时候,又发生了一个年轻代收集。一旦发生这种状况,老年代并发收集会被年轻代收集所中断,直到后者结束以后马上恢复执行。CMS GC 的缺省年轻代收集器被称为ParNew 收集器。

图1.3 描述了因为年轻代垃圾收集(黑色箭头)、CMS 初始标记,从新标记阶段以及老年代垃圾收集stop-the-world 阶段,致使Java 应用线程被暂停。CMS 垃圾收集器的老年代收集活动从一个stop-the-world 的初始标记阶段开始。一旦完成初始标记,就进入并发标记阶段,在这个阶段容许Java 应用线程和CMS 标记线程同时执行。图1.3 中,在“标记/预清理”标签下方,前两个比较长的黑色箭头就是并发标记线程。一旦并发标记完成,CMS线程就执行并发预清理,即图中“标记/预清理”标签下方两个较短的黑色箭头。须要注意的是,若是有足够的可用硬件线程,CMS 线程的执行成本并不会对Java 应用线程的性能产生太大影响。但若是硬件线程是饱和的或被高度利用的,CMS 线程就会和Java 应用线程竞争CPU 周期。一旦并发预清理完成,stop-the-world 的从新标记阶段就会开始。在从新标记阶段会标记初始标记、并发标记以及并发预清理过程当中可能错过的对象。当从新标记阶段结束后,并发清除启动,释放全部死亡对象的空间。

使用CMS 垃圾收集器面临的一个挑战就是要在应用消耗完Java 的可用堆空间以前完成并发收集工做。所以对CMS 来讲有个很棘手的部分,就是找到一个合适的时机来启动这个并发工做。这种并发方式每每致使一个结果,就是处理同一个应用,CMS GC 会比并行GC 多占用10%~20%的Java 堆空间。这也是为了缩短垃圾收集暂停时间所付出的代价。

CMS 垃圾收集器的另外一个挑战是如何处理老年代中的空间碎片,也就是当老年代中对象间的空间碎片过小,以致于没法容纳从年轻代晋升上来的对象,由于在CMS 的并发收集循环中并不执行压缩,哪怕是增量或局部压缩。一旦没法找到可用空间,就会使CMS 回过来使用串行GC,触发一次full 收集,致使一个漫长的暂停。伴随CMS 碎片的另外一个很不幸的挑战就是上述问题彻底没法预测。一样都是老年代碎片,某些应用可能没有经历过一次full GC,而有些可能时不时就要经历一次。

对CMS 垃圾收集器作些调整,对应用作些优化改动,诸如避免生成大尺寸对象,会有助于延缓空间碎片的产生。固然,调优本就是个不寻常的工做,对专业能力有很高的要求,因此改动应用来避免碎片也是个不小的挑战。

相关文章
相关标签/搜索