每周一次的例行发版,照常来临。我熟悉的打开发版系统,同时也打开了服务监控系统,好让别人看的出来我是一个对业务负责的小伙子。吨吨吨,吨吨吨,一顿操做...妈耶,这是咋回事,下游服务dubbo请求超时这么多,看了看个人劳力土,再看了看监控,一分钟400+的请求超时,请求超时数是原来的10倍,上升到了百的量级...java
故事发生在这周二的深夜。当时准备上一个需求,会致使服务中单个线程的IO变长,这个是预知的。在灰度中,发现了下游的服务dubbo请求超时数增长,脑海中立马想到了去查了底层存储的监控,发现底层存储服务的999线都正常,凭借着多年的经验,脑海中一个声音告诉我必定是服务GC出问题了。程序员
而后看了服务的GC,果真单次gc的时间比以前增长了很多。面试
当时立马找了下游的服务,告诉我这部分的超时是在范围容许以内的,内心终于松了一口气。因而继续搞其余的业务去了,虽然我故做淡定,其实当时内心已经慌的不行了。算法
这时候机智的你是否是说不行就扩容呗,但我是那么容易妥协的人吗?这不是考验我内功的时候吗?虽然我平时干着CRUD的活,但哪个程序员不喜欢研究各类技术呢?这是全部程序员的浪漫!因而忙完了手头上的活以后开始了gc调优。bash
-XX:+PrintGCDetails
参数,将每次的GC的耗时信息打印出来。当时的GC日志:并发
%xwEx[GC pause (G1 Evacuation Pause) (young), 0.0962103 secs]
[Parallel Time: 23.3 ms, GC Workers: 4]
[GC Worker Start (ms): Min: 146441.2, Avg: 146441.3, Max: 146441.3, Diff: 0.1]
[Ext Root Scanning (ms): Min: 1.5, Avg: 1.8, Max: 2.4, Diff: 1.0, Sum: 7.2]
[Update RS (ms): Min: 1.0, Avg: 1.5, Max: 1.7, Diff: 0.6, Sum: 5.9]
[Processed Buffers: Min: 27, Avg: 34.8, Max: 41, Diff: 14, Sum: 139]
[Scan RS (ms): Min: 0.3, Avg: 0.3, Max: 0.3, Diff: 0.0, Sum: 1.3]
[Code Root Scanning (ms): Min: 0.0, Avg: 0.5, Max: 1.1, Diff: 1.1, Sum: 1.9]
[Object Copy (ms): Min: 18.3, Avg: 19.0, Max: 19.6, Diff: 1.2, Sum: 76.1]
[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Termination Attempts: Min: 1, Avg: 1.8, Max: 3, Diff: 2, Sum: 7]
[GC Worker Other (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.1, Sum: 0.3]
[GC Worker Total (ms): Min: 23.1, Avg: 23.2, Max: 23.2, Diff: 0.1, Sum: 92.7]
[GC Worker End (ms): Min: 146464.4, Avg: 146464.4, Max: 146464.5, Diff: 0.1]
[Code Root Fixup: 0.1 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.7 ms]
[Other: 72.1 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 69.9 ms]
[Ref Enq: 0.6 ms]
[Redirty Cards: 0.1 ms]
[Humongous Register: 0.1 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.8 ms]
[Eden: 4824.0M(4824.0M)->0.0B(4820.0M) Survivors: 88.0M->92.0M Heap: 5044.1M(8192.0M)->224.1M(8192.0M)]
[Times: user=0.17 sys=0.00, real=0.09 secs]
复制代码
当时GC中基本都是young GC类型的日志,所以不存在Mixed GC中对于old genaration回收所形成的gc时间增长。而后发现了Ref Proc
这一过程消耗的比较久,而后迅速的想到了并行开启-XX:+ParallelRefProcEnabled
,而且调大了并行标记的线程-XX:ConcGCThreads
,以及增长了-XX:G1HeapRegionSize
,一套下来行云流水,开始了枯燥的发版过程。果真一切都在个人预料之中:oracle
等等,我这就完了?固然不是,个人目的是想让你们看了这篇以后就知道之后如何去优化高并发服务的gc问题,我敢说即便不少在一线大厂中打拼了不少年的老开发,对gc参数调优这块也是只知其一;不知其二,所以这部分的知识会了,能够很容易在面试以及在工做中脱颖而出!我会在下面的篇幅中按部就班,由浅入深的讲解关于虚拟机的GC。高并发
目前大多数的公司开发都是基于jdk8,主流使用的GC收集器主要是G1
,这里拿CMS
和G1
进行简单的对比,来突出G1
这款收集器是多么的优秀!可能有的小公司使用的Parallel
收集器,可是Parallel
真的是一款毫无特点的收集器,用它跟G1
对比简直差距太大了。这里只先作对比,相关细节后续在详解,这里只是突出为何大多数公司选择G1
的缘由。布局
所以经过对比发现,从业务的角度考虑,G1优势不言而喻,分代收集,不容易产生内存碎片化,一次回收比较完全,不容易Full gc,并发标记,停顿时间相对可控(基于停顿时间和region占用大小判断是否有价值回收)。优化
那么能够这样来理解整个GC过程,对于young gc而言,将整个young geenration的对象进行可达性分析,若是发现对象可达,则标记该对象是存活,若是不可达,则标记对象为dead,而后将标记为存活的对象拷贝到survivor regions或者old regions中,将dead objects所处的regions回收。
对于Mixed Gc而言,除了会进行上述的young GC中的对于整个young generation进行标记以及拷贝以外,还会对部分old generation中的old regions进行回收,回收的算法采用的是标记--整理,即尽量的释放出连续的内存空间范围。
youg-only phase
:将Young regions中的对象提高到Old,存到到Old regions中,而后回收young regions。space-reclamation phase
:常说的Mixed GC中的一个阶段。不只对young regions进行回收,也会筛选有价值的Old regions进行回收。这两个阶段之间是能够转换的,当老年代的内存使用空间与总老年代空间的百分比超过了必定的阈值(后面篇幅中会讲解),不只会对young generation进行回收,也会触发对老年代的回收。
说白了就是超过这个阈值以后,会先触发young-only phase
阶段,而后young GC完了以后在进行space-reclamation phase
阶段,对老年代进行回收;若是老年代的使用空间占总得老年代空间没有到达阈值,则不会触发space-reclamation phase
,只会继续开启下一个youg-only phase
,进行循环。
youg-only phase
的停顿
initial Mark:标记GC Roots能直接关联到的对象,停顿时间很短。标记以后会进入Concurrent Marking(不须要停顿),并发标记做用是肯定Old regions中的全部存活对象是否须要在接下来的space-reclamation phase
保留。初始标记这一过程可能尚未跑完,另外一个不包含初始标记的young gc过程可能就已经开始干活了。初始标记并不必定发生在全部的young gc中,只有老年代的使用空间占总老年代空间大小超过了InitiatingHeapOccupancyPercent,才会触发初始标记。
Remark:这一个停顿主要是为了完成标记的过程。由于上一个过程是并发标记,是跟随用户线程一块儿跑的,所以可能就会并发标记事后,一些对象随着用户线程的执行,可达性发生了变化,所以须要用户线程停下来,去清理一些浮动垃圾。这一过程主要处理reference processing
和一些class的卸载,虽然停顿,可是能够有多个标记线程。
CleanUp:也须要停顿。主要进行region的回收,且对空的region也会进行回收。而且肯定是否要接着进行space-reclamation phase
阶段。若是须要进行space-recalmation phase
,则继续进行,而后cleanUp部分young regions和old regions(有回收价值的old region)。若是肯定不须要接着进行space-reclamation phase
,则当前的cleanup阶段就至关因而youg-only phase
的cleanUp,只是单纯的表示对young generation的cleanUp已经完成了。
space-reclamation phase
的停顿
好比Colletions(能够理解可能须要被回收的region的集合,而且一个GC周期中可能有多个这样的集合)中的老年代object引用了新生代的object,那么新生代对象所处的region就会把老年代的region记录进新生代region维护的Remembered set中。所以space-reclamation phase
阶段不只会对新生代object的region进行GC,也会分析新生代region背后的Remembered set,挑选出回收性价比高的一些old regions进行回收。
到此为止,我已经把我知道的G1的回收机制都告诉你了,若是你对关于G1回收的算法感兴趣,好比G1标记算法Snapshot-At-The-Beginning
等想进一步了解,能够去oracle官网中查看,后续有时间,我也会出一期这样的文章。接下来我会针对不一样的GC场景进行分析,以及给出相关的建议。坐稳了,发车!
这是G1(大多数收集器也一样)的一个兜底GC的阶段。缘由主要是太多old generation的内存占用致使,好比程序中使用了某一个模板解析引擎,可是没有提取变量,致使每一次执行都是编译一个新的class,这种状况就很容易致使Full GC,或者有不少humongous objects的存在。若是程序中发生了Full gc,除了GC相关的调优,也须要多花时间去优化你的业务代码。
直白一点就是由于建立了太多的object,致使g1不能及时的去回收。常见的场景是Concurrent Marking
没有及时complete。
所以Full GC的优化思路主要分为两个方面:
Full GC调优Tips:
若是多是大对象太多形成的,能够gc+heap=info
查看humongous regions个数。能够增长经过-XX:G1HeapRegionSize
增长Region Size,避免老年代中的大对象占用过多的内存。
增长heap大小,对应的效果G1能够有更多的时间去完成Concurrent Marking
。
增长Concurrent Marking
的线程,经过-XX:ConcGCThreads
设置。
强制mark阶段提前进行。由于在Mark阶段以前,G1会根据应用程序以前的行为,去肯定the Initiating Heap Occupancy Percent
(IHOP
)阈值大小,好比是否须要执行initial Mark
,以及后续CleanUp
阶段的space-reclamation phase
;若是服务流量忽然增长或者其余行为改变的话,那么基于以前的预测的阈值就会不许确,能够采起下面的思路:
IHOP
分析过程当中的所须要的内存空间,经过-XX:G1ReservePercent
来设置,提升预测的效率。IHOP
分析机制,-XX:-G1UseAdaptiveIHOP
,而后手动的指定这个阈值大小,-XX:InitiatingHeapOccupancyPercent
。这样就省去了每次预测的一个时间消耗。Full gc多是系统中的humongous object比较多,系统找不到一块连续的regions区域来分配。能够经过-XX:G1HeapRegionSize
增长region size,或者将整个heap调大。
gc日志中能够看Ref Proc
和Ref Enq
,Ref Proc
G1根据不一样引用类型对象的要求去更新对应的referents;Ref Enq
G1若是实际引用对象已经不可达了,那么就会将这些引用对象加入对应的引用队列中。若是这一过程比较长,能够考虑将这个过程开启并行,经过-XX:+ParallelRefProcEnabled
。
young-only
回收较久主要缘由是Collection Set
中有太多的存活对象须要拷贝。能够经过gc日志中的Evacuate Collection Set
看到对应的时间,能够增长young geenration的最小大小,经过-XX:G1NewSizePercent
。 也多是某一个瞬间,幸存下来的对象一会儿有不少,这种状况会形成gc停顿时间猛涨,通常应对这种状况经过-XX:G1MaxNewSizePercent
这个参数,增长young generation最大空间。
Mixed
回收时间较久经过开启gc+ergo+cset=trace
,若是是predicated young regions
花费比较长,能够针对上文中的方法。若是是predicated old regions
比较长,则能够经过如下方法:
-XX:G1MixedGCCountTarget
这个参数,将old generation的regions分散到较多的Collection(上文有解释)中,增长-XX:G1MixedGCCountTarget
参数值。避免单次处理较大块的Collection。那么如今回过头去看我以前调整的参数,是否是明白了我调整了以后,服务的GC效率立马提高了呢?其实调优的过程不是一蹴而就的,须要持续打磨,有了经验以后,你看到以后想到的东西永远比别人多!
手写辛苦,欢迎各位点个赞留言,也顺带恭喜FPX和TES携手共进决赛哈哈。