美文网首页
记一次线上JVM长时间STW之分析(上)

记一次线上JVM长时间STW之分析(上)

作者: 华安火车迷 | 来源:发表于2017-10-26 18:28 被阅读0次

    这两天再捣鼓一个JVM长时间STW的问题,看了不少文章,怕过两天就忘掉了,还是写点东西记录下来,虽然到目前这一刻还是没找到root cause,但是期间却学到不少东西,记下来也是很有价值的。

    话不多说,先上问题


    在线上的JVM我们发现最近一些比较奇怪的毛刺,在此之前,我们遇到一些GC 的毛刺,是因为没有硬性配置CMS的Initial Mark的时刻导致,根据JVM自己的算法,把CMS启动时间搞到90% 多的HEAP时刻,导致回收失败总是触发Full GC导致,后来通过硬性配置 CMS的Initial Mark 解决了那个毛刺的问题。看上图可知,这次明显不是GC惹的祸。
    从图中可以看到,在偶然的一些时刻,会有非常长时间的STW时间(1.5秒),而从下图看到,虽然这次的STW是由GC触发的(GenCollectForAllocation),但是时间杀手并不是GC, 从下图打印的SafePoint statistics 的table看到,时间都花在了 block阶段,也就是在等待JVM线程进入安全点导致,而GC仅仅用了116毫秒。
    网上google了一把,几乎没有人提问过为啥block很长这种情况,因此只好去挖挖文章,学习一下JVM的SafePoint原理了。

    首先是先去了解什么是SafePoint,挖到一篇不错的文章

    JVM源码分析之安全点safepoint

    简单总结几句就是说,我们通常以为,JVM STW 很多情况都是因为要GC,其实不然,在JVM里需要STW的情况有下面几种

    • Garbage collection pauses
    • Code deoptimization
    • Flushing code cacheClass redefinition (e.g. hot swap or instrumentation)
    • Biased lock revocation
    • Various debug operation (e.g. deadlock check or stacktrace dump)

    而,在STW之前,JVM需要做的第一件事,就是去把所有的JVM Thread block住,专业点来讲,就是在JVM里安排了一个安全的代码区域叫SafePoint,只要线程跑到了这些安全域,就认为挂起这些线程是安全的。这篇文章详细讲解了几种进入安全域的方法,这里就不再赘述了。

    那这个进入SafePoint是不是一个好惹的鸟呢,好惹的话哥就不用写这篇文章了,也不会有这么多程序员被拿去祭天了,进入SafePoint的每个阶段代表什么呢,又有哪些手段可以检测呢?(比如我上面贴问题那两张图),接着我又去拜读了江南白衣文章

    JVM的Stop The World,安全点,黑暗的地底世界

    上面文章降到可以如何去检测这个ApplicationStopTime,并且解释了那几个字段的含义,这里为了好看,也贴一下:

    此日志分两段,第一段是时间戳,VM Operation的类型,以及线程概况
    total: 安全点里的总线程数
    initially_running: 安全点时开始时正在运行状态的线程数
    wait_to_block: 在VM Operation开始前需要等待其暂停的线程数

    第二行是到达安全点时的各个阶段以及执行操作所花的时间,其中最重要的是vmop
    spin: 等待线程响应safepoint号召的时间
    block: 暂停所有线程所用的时间
    sync: 等于 spin+block,这是从开始到进入安全点所耗的时间,可用于判断进入安全点耗时
    cleanup: 清理所用时间
    vmop: 真正执行VM Operation的时间

    本人语文理解能力比较差,所以当时看到spin 和block,我的理解spin就是JVM发出global SafePoint响应时,所有线程都接收到这个信号的时间,而block 就是所有线程需要跑到安全点的时间。
    这就直接导致了我去想问题的方向,最终是错误的,知道后来又找到另外一篇文章的解释

    java GC进入safepoint的时间为什么会这么长?

    在这篇文章,它是这么介绍的

    2.2 Stop The World的四个阶段
    根据-XX:+PrintSafepointStatistics –XX:PrintSafepointStatisticsCount=1 参数虚拟机打印的日志文件可以看出,safepoint的执行一共可以分为四个阶段:

    • Spin阶段。因为jvm在决定进入全局safepoint的时候,有的线程在安全点上,而有的线程不在安全点上,这个阶段是等待未在安全点上的用户线程进入安全点。
    • Block阶段。即使进入safepoint,用户线程这时候仍然是running状态,保证用户不在继续执行,需要将用户线程阻塞。http://blog.csdn.net/iter_zc/article/details/41892567** 这篇bog详细说明了如何将用户线程阻塞。
    • Cleanup。这个阶段是JVM做的一些内部的清理工作。
    • VM Operation. JVM执行的一些全局性工作,例如GC,代码反优化。
      只要分析这个四个阶段,就能知道什么原因导致的STW时间过长。

    再加上Ivy今天的指点,发现和我之前的理解是出入非常大的,最后我是这么理解的,Spin阶段其实就是所有线程跑到了SafePoint 的时间(这个时间一般有长有短,具体看你的JVM 线程都在干嘛,比如线程再跑右边界的大for循环,那么就得等了,这也是在网上能搜到的一般程序员遇到的,就是Spin很长导致最后Sync很长),而 block 阶段则是JVM让所有线程 Block锁花费的时间了,具体JVM是怎么让JVM Thread 阻塞的呢?推荐仔细看下面两篇文章:

    聊聊JVM(九)理解进入safepoint时如何让Java线程全部阻塞
    聊聊JVM(六)理解JVM的safepoint

    如果简单几句话描述的话,就是要视乎当前的JVM Thread在跑什么,比如Java Code? 还是Native调用? 等,然后措施就是让这个线程去OS 去load 一个锁住的Page, 这样的话这个线程发送内核命令去Load Page却Load不到,自然而然就挂起了。

    那再回过来我们的问题,怀疑的点就变成很窄了,原本这个系统调用是非常迅速的,很可能就是OS在做自己的IO,比如swapping等,进行了进程上下文的切换,导致根本想去响应这个调用,因此再回来时自然这个操作就耗时了(还记得这个问题吗?就是block 用了1.3s)。
    由于线上我们并没有配置SafePoint超时和配置SafePoint超时打印线程状态,因此我们也只能猜测了,最后一种可能就是线程在做状态切换时被阻塞了,注意,这里所说的线程状态是JVM里面对应的VMThread ,而不是传统的Java Thread,大家可以看看下面这篇文章:

    聊聊JVM(五)从JVM角度理解线程

    看个大概吧,具体深入的CPP我也没仔细去看了,又和Ivy讨论了一番,最后得出结论是,这个损耗应该也是计算在spin里面的而不是block 的耗时。


    后话

    猜测终归是猜测,在实验室环境压测了2天,也没抓到什么罪证,最大的可能的的确确就是OS在进行上下文切换了,看对症下药做点措施再继续观察了。
    收官时刻,突然院长又冒出另外一个问题(巨坑啊啊啊啊啊):CMS 如果不配置 Eden比例的话,默认是很小的,终于发现我们的频繁的YGC是有原因的,哈哈哈,今天一并把这个也该了,希望上面遇到的问题也是因为YGC太频繁而导致的。

    目前是加上了 -XX:NewRatio=2 了,GC看起来平滑了N倍。

    image.png

    至于这个问题:还是等解决了一切其他问题后再回来看吧。。。。。。。。

    (待续)

    相关文章

      网友评论

          本文标题:记一次线上JVM长时间STW之分析(上)

          本文链接:https://www.haomeiwen.com/subject/nqikpxtx.html