idea的一次jvm調優

1.準備工作:

這段時間一直在看jvm調優的內容,苦于沒有環境讓自己練手,周三下午測試人員說測試后臺過于慢,之前一直以為是數據庫壓力太大的問題,后來連上測試環境,發現xmn=150m,新生代只分配了150m,而整個堆最大為2g,然后和運維同事溝通了,建議設置成1G,項目再打包,后臺頁面基本都秒開,雖然是個測試環境小小的調優,還是比較開心的哈.
測試環境畢竟是測試環境,因為開發沒有權限,有東西想改還是要和運維溝通,這幾天拿idea試試手,把idea調優下.先說下我的配置,E5 2660 ,8核16線程,16G內存,120G ssd,cpu雖然線程數比較多,但是主頻比較低,只有2.2g,idea在我電腦上啟動大概需要20s左右.其實前幾天就著手做這個事情,但是一直沒有辦法統計出idea具體的啟動時間,周志明大佬在深入jvm一書中是用的eclipse調優,eclipse插件似乎把啟動時間寫入到全局變量中了,idea我找了半天沒找到,今天下午突然發現,其實idea在啟動的時候會把啟動信息寫入到日志中,我的位置是在C:\Users\Administrator.IntelliJIdea2018.1\system\log\idea.log,這里面記錄了idea啟動都做了哪些事情
image
然后我就以app initialzation took作為時間的標準,后面這個13745ms我不知道它咋算出來的,啟動時間到這個點輸出一個是11667這個時間才對,然后我就以11667作為idea的啟動時間來判斷.如果是用linux來截取這個時間的話awk命令就夠了,做兩次管道
grep 'App initialization took' idea.log | awk '{print $4}'|awk -F ']' '{print $1}'
我是windows上跑的這個,然后就開了個pycharm,用python來處理這個時間,為了能夠在idea啟動的瞬間,就記錄這些信息,我把idea配置在了環境變量里,然后用python啟動,就是下面的openIdeaAndTools方法,這里順便吐槽下pycharm,我配完環境變量,idea死活起不來,然后電腦重啟下突然就好了,估計它的terminal緩存了系統的環境變量,不知道算不算bug.
#!/usr/bin/env python
# -*- coding=utf-8 -*-
import fileinput
import os
import threading

import time


class MyThread(threading.Thread):
    def __init__(self, arg):
        super(MyThread, self).__init__()
        self.arg = arg

    def run(self):
        os.system(self.arg)


def getIdeaStartTime(logPath):
    # 獲取idea的啟動時間
    for line in fileinput.input(logPath):
        if 'App initialization took' in line:
            print(line.split('[')[-1].split(']')[0])


def openIdeaAndTools():
    # 這里調用命令是阻塞執行,所以開啟線程異步執行
    t1 = MyThread('idea64.exe ')
    t1.start()
    # 防止idea未啟動先執行了下面的代碼
    time.sleep(0.1)
    try:
        # 調用jps命令
        pid = os.popen("jps")
        for l in pid:
            # 因為idea啟動后,jps輸出的只有一個vmid,后面沒有類名,以此來判斷該進程是idea的
            id = l.split(' ')[0]
            info = l.split(' ')[1]
            # 我這臺機器上還跑了pycharm,pycharm后面同樣沒有類名,就把pycharm的vmid去掉
            if not info.strip() and int(id) != 1200:
                print("idea的Id為" + id)
                # 調用jstat
                t4 = MyThread('jstat -gcutil ' + id + ' 1000 3')
                t4.start()
                # 調用jconsole
                t2 = MyThread('jconsole ' + id)
                # 調用jvisualvm
                t3 = MyThread('jvisualvm')
                t3.start()
                t2.start()
    finally:
        pid.close()


if __name__ == "__main__":

    # getIdeaStartTime(logPath = "C:\\Users\\Administrator\\.IntelliJIdea2018.1\\system\\log\\idea.log")
    openIdeaAndTools()

2.未優化前:

首先說下idea的默認配置,在help-edit custom vm options可以看到,配置如下:
-Xms128m                        //最小堆128
-Xmx750m                        //最大750
-XX:ReservedCodeCacheSize=240m  //代碼緩存
-XX:+UseConcMarkSweepGC         //使用parnew+cms垃圾回收
-XX:SoftRefLRUPolicyMSPerMB=50  //和soft引用有關
-ea
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-XX:+HeapDumpOnOutOfMemoryError //內存溢出dump出快照
-XX:-OmitStackTraceInFastThrow  //拋出堆棧異常
整個的設置還是比較少的,經過多次測試,idea的啟動時間大概在18s左右,jstat信息如下(只截取了最后一次測試的前18s)
idea的Id為4440
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00 100.00  63.01   1.64  94.90  83.95      1    0.011     0    0.000    0.011
  0.00 100.00  61.75  16.45  95.42  83.95      3    0.067     0    0.000    0.067
100.00   0.00  28.84  22.12  94.73  83.02      4    0.106     0    0.000    0.106
100.00   0.00  68.97  22.12  94.73  83.02      4    0.106     0    0.000    0.106
  0.00 100.00  30.26  31.35  96.83  93.83      5    0.117     1    0.005    0.122
100.00   0.00   0.00  35.89  97.20  94.96      6    0.131     2    0.027    0.158
 90.56   0.00  10.54  38.82  97.53  95.00      8    0.141     2    0.027    0.168
100.00   0.00  68.57  46.48  97.53  95.04     10    0.165     2    0.027    0.192
  0.00  97.85  22.65  51.15  97.05  92.71     11    0.175     2    0.027    0.202
  0.00 100.00  73.74  60.28  97.18  94.92     13    0.225     2    0.027    0.252
  0.00 100.00  91.61  60.28  97.18  94.92     13    0.225     2    0.027    0.252
100.00   0.00   4.12  64.82  97.41  93.94     14    0.235     2    0.027    0.262
100.00   0.00  12.22  64.82  97.41  93.94     14    0.235     2    0.027    0.262
100.00   0.00  71.19  64.82  97.41  93.94     14    0.235     2    0.027    0.262
  0.00 100.00  43.52  78.72  96.97  92.43     17    0.315     3    0.029    0.343
  0.00 100.00   6.02  74.14  95.99  91.92     19    0.341     5    0.053    0.395
100.00   0.00  35.30  67.15  95.24  90.23     20    0.370     6    0.092    0.462
100.00   0.00  35.64  78.42  94.32  87.14     22    0.419     7    0.094    0.512
這18s中可以看出YGC(也就是minor gc)發生了22次,共耗時0.419s,而FGC總共發生了7次,總耗時0.094s,ygc平均每次時間為0.019s,而fgc每次為0.013.
image
從這張圖中我們可以看出整個堆的大小先是平穩,后隨著使用堆大小的增加逐步變大,而使用堆頻繁gc,gc日志如下(前23s,比較長,就直接放服務器了):GC日志:
在0.942s的時候第一次出現了Allocation Failure,分配內存失敗,這會導致新生代觸發gc,這主要的原因是因為初始堆的大小設置成了128m.好了,有了解決思路,第一步要做的就是先禁掉插件,哈哈,人生就是需要皮一下,idea的log反應出插件的加載似乎是占用了很長一段時間,為了達到優化的效果,先把插件禁掉,在來對gc進行調優.

3.開工:

3.1禁插件

禁掉插件的效果非常明顯,測試了三次,時間分別為9296,10034,10363,而最后一次jstat的信息如下:
idea的Id為6512
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00 100.00  91.18   1.63  94.81  83.95      2    0.028     0    0.000    0.028
  0.00 100.00  49.32  16.38  95.35  83.95      3    0.068     0    0.000    0.068
  0.00 100.00  50.65  16.38  95.35  83.95      3    0.068     0    0.000    0.068
100.00   0.00  29.37  21.33  94.72  85.34      4    0.089     0    0.000    0.089
100.00   0.00  49.98  21.33  94.72  85.34      4    0.089     0    0.000    0.089
100.00   0.00  90.62  21.33  94.72  85.34      4    0.089     1    0.009    0.099
  0.00 100.00  45.10  32.07  97.56  94.05      5    0.112     1    0.009    0.121
100.00   0.00  79.42  36.54  97.22  95.07      6    0.128     2    0.035    0.163
 90.65   0.00  92.93  39.97  97.63  95.09      9    0.169     2    0.035    0.204
100.00   0.00  62.49  47.90  97.69  95.15     10    0.195     2    0.035    0.230
10s內發生了10次ygc,2次fgc,其實說實話,這樣的性能完全很好了,但是本著這是一篇調優的文章,有點參數還是要改改,看看效果,首先調整的就是整個堆的大小.

3.2調整堆的大小

啟動的時候頻繁出現了allocation failure,這是因為堆的初始大小過小,導致新生代的內存過小,所以,先設置了以下參數:
-Xms2048m
-Xmx2048m
-Xmn1600m
堆的大小不在動態調整,而是固定在了2g,而新生代則直接分配了1600m,這一次,啟動時間為7s,jstat信息如下,前12s:
S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00   0.00  16.00   0.00  17.24  19.25      0    0.000     0    0.000    0.000
  0.00   0.00  24.00   0.00  17.24  19.25      0    0.000     0    0.000    0.000
  0.00   0.00  32.00   0.00  17.24  19.25      0    0.000     0    0.000    0.000
  0.00   0.00  36.00   0.00  17.24  19.25      0    0.000     1    0.101    0.101
  0.00   0.00  46.00   0.00  17.24  19.25      0    0.000     1    0.101    0.101
  0.00   0.00  54.00   0.00  17.24  19.25      0    0.000     1    0.101    0.101
  0.00   0.00  66.00   0.00  17.24  19.25      0    0.000     1    0.101    0.101
  0.00   0.00  70.00   0.00  17.24  19.25      0    0.000     1    0.101    0.101
  0.00   0.00  78.00   0.00  17.24  19.25      0    0.000     2    0.101    0.101
  0.00   0.00  90.00   0.00  17.24  19.25      0    0.000     2    0.258    0.258
  0.00   0.00  96.00   0.00  17.24  19.25      0    0.000     2    0.258    0.258
  0.00  41.29  20.32   0.00  96.90  94.00      1    0.054     3    0.267    0.321
一直到12s的時候,新生代才出現首次gc,gc日志異常的少,前面一直是cms在做初始標記-并發標記等工作,是在處理老年代內存,前7s只出現了一次老年代的回收,這也說明了大內存對程序的運行幫助那是相當大.
4.246: [GC (CMS Initial Mark) [1 CMS-initial-mark: 0K(458752K)] 471859K(1933312K), 0.1010486 secs] [Times: user=0.16 sys=0.00, real=0.10 secs] 
4.347: [CMS-concurrent-mark-start]
4.347: [CMS-concurrent-mark: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
4.347: [CMS-concurrent-preclean-start]
4.349: [CMS-concurrent-preclean: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
4.349: [CMS-concurrent-abortable-preclean-start]
 CMS: abort preclean due to time 9.362: [CMS-concurrent-abortable-preclean: 0.256/5.013 secs] [Times: user=13.81 sys=2.18, real=5.01 secs] 
9.362: [GC (CMS Final Remark) [YG occupancy: 1022364 K (1474560 K)]9.362: [Rescan (parallel) , 0.1397593 secs]9.502: [weak refs processing, 0.0000532 secs]9.502: [class unloading, 0.0081261 secs]9.510: [scrub symbol table, 0.0068646 secs]9.517: [scrub string table, 0.0007781 secs][1 CMS-remark: 0K(458752K)] 1022364K(1933312K), 0.1575112 secs] [Times: user=1.06 sys=0.00, real=0.16 secs] 
9.520: [CMS-concurrent-sweep-start]
9.520: [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
9.520: [CMS-concurrent-reset-start]
9.523: [CMS-concurrent-reset: 0.003/0.003 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
12.028: [GC (Allocation Failure) 12.028: [ParNew: 1310720K->67643K(1474560K), 0.0544422 secs] 1310720K->67643K(1933312K), 0.0545925 secs] [Times: user=0.51 sys=0.06, real=0.06 secs] 
12.083: [GC (CMS Initial Mark) [1 CMS-initial-mark: 0K(458752K)] 106973K(1933312K), 0.0083509 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
12.091: [CMS-concurrent-mark-start]
12.091: [CMS-concurrent-mark: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
12.091: [CMS-concurrent-preclean-start]
12.094: [CMS-concurrent-preclean: 0.003/0.003 secs] [Times: user=0.16 sys=0.01, real=0.00 secs] 
12.095: [CMS-concurrent-abortable-preclean-start]
16.748: [CMS-concurrent-abortable-preclean: 4.303/4.653 secs] [Times: user=32.20 sys=1.31, real=4.65 secs] 
16.748: [GC (CMS Final Remark) [YG occupancy: 737368 K (1474560 K)]16.748: [Rescan (parallel) , 0.0331714 secs]16.782: [weak refs processing, 0.0000574 secs]16.782: [class unloading, 0.0177730 secs]16.800: [scrub symbol table, 0.0229479 secs]16.823: [scrub string table, 0.0011784 secs][1 CMS-remark: 0K(458752K)] 737368K(1933312K), 0.0792557 secs] [Times: user=0.48 sys=0.00, real=0.08 secs] 
16.828: [CMS-concurrent-sweep-start]
16.828: [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
16.828: [CMS-concurrent-reset-start]
16.829: [CMS-concurrent-reset: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
21.380: [GC (Allocation Failure) 21.380: [ParNew: 1378363K->124879K(1474560K), 0.0719474 secs] 1378363K->124879K(1933312K), 0.0721093 secs] [Times: user=0.53 sys=0.08, real=0.07 secs] 
23.844: [GC (Allocation Failure) 23.845: [ParNew: 1435599K->78246K(1474560K), 0.2064758 secs] 1435599K->139746K(1933312K), 0.2066017 secs] [Times: user=1.06 sys=0.03, real=0.21 secs] 

3.2 新生代調優

為了能夠說明某些問題,我把參數又調回了初始參數,因為如果用上面的參數繼續跑,基本沒法調了,用原始參數對新生代進行調優,在此首先需要介紹下parnew收集器.

3.2.1 parnew介紹

parnew是作為新生代的垃圾回收器,當然也就采用了復制的算法,首先進行可達性分析,stop the world,然后再進行垃圾回收,將eden和s0的數據復制到s1當中去,它和serial的區別就在于parnew是多線的,然后關于parnew的文檔,很可惜的是我在oracle官網上沒有找到,全是介紹g1,cms和parallel scavenge的,但是官網給出的常見問題列表中有個問題比較有意思,地址, What is the Parallel Young Generation collector (-XX:+UseParNewGC)?官網的回復是parNew和parallel表現出來的東西很像,但是在內部實現上不一樣,也就是說關于運行的機制大都差不多,畢竟都是并行的,但是在代碼實現上差很多,還有一個不同點就是parnew是能和cms搭配使用的,而parallel不行.

3.2.2調優參數

然后我就用了初始的vm參數中又加了幾個參數,(jvm的參數值介紹,官網地址)
第一個參數是-Xmn400m,分配給新生代400m的內存,另外固定住堆的大小為750M,防止因堆的大小不夠用而頻繁gc.
第二個是-XX:ParallelGCThreads,這個參數用于調整線程數,在我機器上默認是13個
image
第三個是-XX:+ScavengeBeforeFullGC,minor gc優先于fgc,這樣的好處就是往往會存在著新生代的對象持有老年代的引用(所以老年代的gc,通常會伴隨著fgc),這樣在fgc之前先清理掉一部分年輕代對象,會減少可達性分析的成本(在本次實驗中,似乎沒有展示出優勢,fgc次數比較少).
-Xms750m
-Xmx750m
-Xmn400m
-XX:ParallelGCThreads=20
-XX:+ScavengeBeforeFullGC

3.2.3調優結果

這一次啟動時間9s,然后看下前9s的jstat信息:
S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00   0.00  28.00   0.00  17.24  19.25      0    0.000     0    0.000    0.000
  0.00   0.00  50.00   0.00  17.24  19.25      0    0.000     0    0.000    0.000
  0.00   0.00  52.00   0.00  17.24  19.25      0    0.000     0    0.000    0.000
  0.00   0.00  60.00   0.00  17.24  19.25      0    0.000     0    0.000    0.000
  0.00   0.00  64.00   0.00  17.24  19.25      0    0.000     0    0.000    0.000
  0.00   0.00  68.00   0.00  17.24  19.25      0    0.000     0    0.000    0.000
  0.00   0.00  76.00   0.00  17.24  19.25      0    0.000     0    0.000    0.000
  0.00   0.00  84.00   0.00  17.24  19.25      0    0.000     1    0.089    0.089
  0.00  66.86   5.20   0.00  96.69  92.35      1    0.025     1    0.089    0.115
  0.00  66.86  33.40   0.00  96.69  92.35      1    0.025     1    0.089    0.115
由上面結果可得知,9s中共發生1次ygc,這我覺得益于-Xms750m,-Xmx750m,-Xmn400m,將新生代的內存大小固定在了400m,而減少了頻繁的gc.

3.2.4parnew gc日志解讀

然后這里順便解讀下gc的日志,關于parnew的:
9.399: [GC (Allocation Failure) 9.399: [ParNew: 327680K->27386K(368640K), 0.0253901 secs] 327680K->27386K(727040K), 0.0255930 secs] [Times: user=0.06 sys=0.06, real=0.03 secs] 
9.399代表項目啟動時間,Allocation Failure代表gc的原因,因為內存分配失敗,ParNew代表使用的ParNew算法,327680K->27386K(368640K)代表,新生代回收前->新生代回收后(整個新生代大小),327680K->27386K(727040K)代表,整個堆回收前->整個堆回收后(整個堆的大小),0.0255930s代表此次垃圾回收的時間,[Times: user=0.06 sys=0.06, real=0.03 secs] 代表用戶線程執行的時間,系統線程執行的時間,總共耗時,因為用戶線程和系統線程并行執行,所以總耗時往往比user+sys時間要小.

3.3 老年代調優

3.3.1 cms介紹

老年代主要用的是cms垃圾回收器,使用的是標記-清除算法,正如之前所說的,major gc時因為存在老年代持有新生代的引用,所以會掃描整個堆而發生fgc,另外,因為標記-清除算法會產生碎片,當老年代的內存足夠分配一個對象,卻因內存不連續時,這時往往也會發生fgc,下面就簡單介紹下cms文章原文地址,CMS官方介紹
cms整個工作過程總共分為這幾個步驟:
1.初始標記,這一階段主要是通過gc root進行可達性分析,找到直接關聯的對象,這是需要stw.
2.并發標記,由第一階段標記出的gc信息出發,追蹤整個堆的對象信息,將所有活著的對象進行標記,因為是并發執行,所以沒有太多的開銷
3.并發預清理,此階段主要是為了減少重標記的時間,標記新生代晉升的對象,新分配到老年代的對象以及在并發階段被修改的對象,重新標記需要全量掃描整個堆大小(存在年輕代關聯老年代的引用,所以需要掃描整個堆),但是如果在并發預清理之后,重標記之前做一次minor gc(也就是類似于之前ScavengeBeforeFullGC)的功能,則能降低重標記的時間.所以它有個子階段.
????3.1 可中斷的并發預清理,這一階段主要的工作就是能夠在remark階段之前產生一次minor gc,由兩個參數控制CMSScheduleRemarkEdenSizeThreshold,CMSScheduleRemarkEdenPenetration,默認值分別是2M,50%,第一個代表當預清理完成之后,eden區域大于這個值時,開啟可中斷的預清理,直到eden空間使用率達到50%則中斷,并發預清理期間執行時長 由CMSMaxAbortablePrecleanTime = 5s 這個參數控制,意為并發預清理執行時長如果有5s了,那無論有無minor gc,有無達到50%,都停止并發預清理,進入重標記階段,如果沒有發生minor gc,則可由CMSScavengeBeforeRemark參數,使并發預清理后,remark之前強制執行一次minor gc(這會造成重標記時間減短,但minor gc是需要stw.所以使用還是要權衡)
4.重標記:會掃描新生代和老年代,日志中提現為Rescan(多線程),這個因為前面做了很多工作,所以停頓時間較少
5.并發清理:并發清理無效對象
6.重置:cms清除內部狀態.為下次回收準備
整個流程大致如此,這其中有幾個點需要說明下:
cms是如何識別老年代對象引用的?這里需要介紹下card table,card table其實就是一個數組,每個位置存儲的是一個byte,cms將老年代空間分為512bytes的快,card table每個元素對應一個塊,并發標記時.如果某個對象的引用發生了變化,就標記該對象所在的快為 dirty card,并發預清理會重新掃描該塊.將該對象引用的對象標記為可達,如圖所示:
初始狀態:
image
隨后老年代引用發生變化,對應的塊變為dirty card:
image
緊接著并發預清理,重新掃描修改后的引用,將其設置為可達:
image
對于老年代持有新生代的引用,minor gc是如何識別的?當有老年代引用新生代,對應的card table 被標識為相應的中(就是card table里面byte的8位,約定好每一位的含義,以此來區分)所以minor gc通過掃描card table就可以識別老年代引用新生代.
何為浮動垃圾?當并發預清理之時,用戶線程還在跑著,此時產生的垃圾只能下次回收,這一部分垃圾叫浮動垃圾.CMSInitiatingOccupancyFraction=n這個參數控制著老年代使用的比例達到n時,才開始垃圾回收,設置過低會造成老年代頻繁的垃圾回收,設置過高會導致無法分配內存空間,會出現Concurrent Mode Failure,從而導致cms使用備份serial old算法清除老年代,從而產生更長的stw.
何為動態檢查機制?UseCMSInitiatingOccupancyOnly,cms根據歷史,預測老年代需要多久填滿以及進行一次回收,老年代使用完之前,cms會根據預測進行gc.
cms如何碎片整理?UseCMSCompactAtFullCollection這個參數,就是進行fgc時,進行碎片整理,但是會造成stw時間變長,CMSFullGCsBeforeCompaction這個參數就是用于設置多少次不壓縮的fgc,緊接著來一次碎片整理.
以上算是對cms的一個總結,cms主要是為了解決兩個問題,一個是減少stw時間,另一個是減少空間碎片,所以性能性對于其他的一些老年代垃圾回收還是比較優秀的.

3.3.2 cms調優

不知道大家注意到沒有,第一次jstat信息老年代的使用空間幾乎沒有怎么使用,但是依舊是發生了fgc,用jstat -gccause可以看到gc原因,如下:
image
我們可以看到出現了Meradata GC threshold,jdk1.8取消了永久代,而是用堆外內存meta space來存放這些信息,而meta回收的及其繁瑣,尤其是類信息,需要判斷整個堆當中是否還有再用的該類的實例,所以這一階段觸發了fgc,而idea默認的MetaspaceSize大小為21M,所以此次對于老年代的調優,先是調大MetaspaceSize=128大小.

3.3.3 調優結果

這一次的效果非常明顯,一直到35s才第一次出現fgc,這一次idea的啟動時間還是11s,但是從gc信息中,我還是比較滿意的,這11s當中只出現了一次ygc,沒有出現fgc.
image

3.3.4 cms日志解讀

這邊主要是以如何分析cms日志為主,截取了一段gc日志:
4.407: [GC (CMS Initial Mark) [1 CMS-initial-mark: 19337K(87424K)] 50969K(126720K), 0.0049123 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
4.407s時初始標記開始,19337k(87424K)->老年代已使用(老年代大小) 50969K(126720K)->整個堆已使用(整個堆大小),0.0049123s共耗時多久,這一階段是需要stw的.
4.412: [CMS-concurrent-mark-start]
4.425: [CMS-concurrent-mark: 0.012/0.012 secs] [Times: user=0.11 sys=0.01, real=0.01 secs]
這一階段是并發標記階段,4.412并發標記開始的標志,4.425開始標記,0.012/0.012因為是并發執行,表示gc線程時間/用戶線程時間
4.425: [CMS-concurrent-preclean-start]
4.428: [CMS-concurrent-preclean: 0.003/0.004 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
4.428: [CMS-concurrent-abortable-preclean-start]**
4.658: [GC (Allocation Failure) 4.659: [ParNew: 39296K->4352K(39296K), 0.0110973 secs] 58633K->31757K(126720K), 0.0112153 secs] [Times: user=0.22 sys=0.00, real=0.01 secs] 
5.741: [CMS-concurrent-abortable-preclean: 0.925/1.312 secs] [Times: user=4.96 sys=0.20, real=1.31 secs] 
這是并發預清理階段,其中含有可中斷的并發預清理,用以讓年輕代發生minor gc,里面的信息和上面的類似,不在贅述.
5.741: [GC (CMS Final Remark) [YG occupancy: 23403 K (39296 K)]5.741: [Rescan (parallel) , 0.0075331 secs]5.749: [weak refs processing, 0.0000928 secs]5.749: [class unloading, 0.0064751 secs]5.755: [scrub symbol table, 0.0063506 secs]5.762: [scrub string table, 0.0010179 secs][1 CMS-remark: 27405K(87424K)] 50808K(126720K), 0.0224856 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 
這是重標記階段,是需要stw的,掃描整個堆 YG occupancy: 23403 K (39296 K)表示年輕代當前使用的(年輕代總容量),rescan是其子階段,開始重新掃描,耗時0.0075331,weak refs processing,第二個子階段,處理弱引用,class unloading,第三個子階段,卸載未使用的類,scrub symbol table,第四份子階段,處理符號表,最后一個子階段scrub string table,處理string池?(后面兩個沒太明白,找了下也沒找到啥有用信息),CMS-remark: 27405K(87424K),在這個階段之后老年代占有的內存大小和老年代的容量;
5.764: [CMS-concurrent-sweep-start]
5.787: [CMS-concurrent-sweep: 0.023/0.023 secs] [Times: user=0.09 sys=0.02, real=0.02 secs]
并發清理階段
5.787: [CMS-concurrent-reset-start]
5.793: [CMS-concurrent-reset: 0.006/0.006 secs] [Times: user=0.08 sys=0.02, real=0.01 secs]
重置一些信息,以便下次垃圾回收.

4.最終的配置信息

-Xms750m
-Xmx750m
-Xmn400m
-XX:MetaspaceSize=128m
-XX:ParallelGCThreads=20
-XX:+ScavengeBeforeFullGC
-XX:ReservedCodeCacheSize=512m
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-XX:+PrintGCDetails
-Xloggc:d:\idea_gc.log
-XX:+PrintGCTimeStamps
-ea
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow

5.總結

如果線上出現長時間停頓的事情,確定是因為gc的原因.首先要確定是gc的某個具體原因,比如新生代或者老年代過小,導致頻繁gc,再比如,就像本次idea調優一樣,因為meta空間過小,導致發生fgc,另外還有一個容易忽略的事情,就是java nio使用了直接內存,這部分內存不受垃圾回收控制,例如下載文件等操作出現長時間卡頓,應該考慮是不是直接內存分配過低的問題.一旦能夠具體到某個原因,在去處理這些事情,效果就會很明確,當然jvm自帶的分析工具是排查問題的主要手段,這些工具一定能夠熟練.
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容