JVM調優引數、方法、工具以及案例總結

等不到的口琴發表於2021-02-08

這種文章挺難寫的,一是JVM引數巨多,二是內容枯燥乏味,但是想理解JVM調優又是沒法避開的環節,本文主要用來總結梳理便於以後翻閱,主要圍繞四個大的方面展開,分別是JVM調優引數、JVM調優方法(流程)、JVM調優工具、JVM調優案例,調優案例目前正在分析,會在將來補上。

垃圾回收有關引數

引數部分,這兒只是做一個總結,更詳細更新的內容請參考Oracle官網:JVM的命令列引數參考

處理器組合引數

關於JVM垃圾處理器區別,參考:JVM調優之垃圾定位、垃圾回收演算法、垃圾處理器對比

-XX:+UseSerialGC = Serial New (DefNew) + Serial Old

適用於小型程式。預設情況下不會是這種選項,HotSpot會根據計算及配置和JDK版本自動選擇收集器

-XX:+UseParNewGC = ParNew + SerialOld

這個組合已經很少用(在某些版本中已經廢棄),詳情參考:Why Remove support for ParNew+SerialOld and DefNew+CMS in the future?

-XX:+UseConc(urrent)MarkSweepGC = ParNew + CMS + Serial Old

-XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8預設) 【PS + SerialOld】

-XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old

-XX:+UseG1GC = G1

Linux中沒找到預設GC的檢視方法,而windows中會列印UseParallelGC

  • java +XX:+PrintCommandLineFlags -version
  • 通過GC的日誌來分辨

Linux下1.8版本預設的垃圾回收器到底是什麼?

  • 1.8.0_181 預設(看不出來)Copy MarkCompact

  • 1.8.0_222 預設 PS + PO

虛擬機器引數

引數名稱 含義 預設值 解釋說明
-Xms 初始堆大小 實體記憶體的1/64(<1GB) 預設(MinHeapFreeRatio引數可以調整)空餘堆記憶體小於40%時,JVM就會增大堆直到-Xmx的最大限制.
-Xmx 最大堆大小 實體記憶體的1/4(<1GB) 預設(MaxHeapFreeRatio引數可以調整)空餘堆記憶體大於70%時,JVM會減少堆直到 -Xms的最小限制
-Xmn 年輕代大小(1.4or lator) 注意:此處的大小是(eden+ 2 survivor space).與jmap -heap中顯示的New gen是不同的。 整個堆大小=年輕代大小 + 年老代大小 + 持久代大小. 增大年輕代後,將會減小年老代大小.此值對系統效能影響較大,Sun官方推薦配置為整個堆的3/8
-XX:NewSize 設定年輕代大小(for 1.3/1.4)
-XX:MaxNewSize 年輕代最大值(for 1.3/1.4)
-XX:PermSize 設定持久代(perm gen)初始值 實體記憶體的1/64
-XX:MaxPermSize 設定持久代最大值 實體記憶體的1/4
-Xss 每個執行緒的堆疊大小 JDK5.0以後每個執行緒堆疊大小為1M,以前每個執行緒堆疊大小為256K.更具應用的執行緒所需記憶體大小進行 調整.在相同實體記憶體下,減小這個值能生成更多的執行緒.但是作業系統對一個程式內的執行緒數還是有限制的,不能無限生成,經驗值在3000~5000左右 一般小的應用, 如果棧不是很深, 應該是128k夠用的 大的應用建議使用256k。這個選項對效能影響比較大,需要嚴格的測試。 和threadstacksize選項解釋很類似,官方文件似乎沒有解釋,在論壇中有這樣一句話:"” -Xss is translated in a VM flag named ThreadStackSize” 一般設定這個值就可以了。
-XX:ThreadStackSize Thread Stack Size (0 means use default stack size) [Sparc: 512; Solaris x86: 320 (was 256 prior in 5.0 and earlier); Sparc 64 bit: 1024; Linux amd64: 1024 (was 0 in 5.0 and earlier); all others 0.]
-XX:NewRatio 年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代) -XX:NewRatio=4表示年輕代與年老代所佔比值為1:4,年輕代佔整個堆疊的1/5 Xms=Xmx並且設定了Xmn的情況下,該引數不需要進行設定。
-XX:SurvivorRatio Eden區與Survivor區的大小比值 設定為8,則兩個Survivor區與一個Eden區的比值為2:8,一個Survivor區佔整個年輕代的1/10
-XX:LargePageSizeInBytes 記憶體頁的大小不可設定過大, 會影響Perm的大小 =128m
-XX:+UseFastAccessorMethods 原始型別的快速優化
-XX:+DisableExplicitGC 關閉System.gc() 這個引數需要嚴格的測試
-XX:MaxTenuringThreshold 垃圾最大年齡 如果設定為0的話,則年輕代物件不經過Survivor區,直接進入年老代. 對於年老代比較多的應用,可以提高效率.如果將此值設定為一個較大值,則年輕代物件會在Survivor區進行多次複製,這樣可以增加物件再年輕代的存活 時間,增加在年輕代即被回收的概率 該引數只有在序列GC時才有效.
-XX:+AggressiveOpts 加快編譯
-XX:+UseBiasedLocking 鎖機制的效能改善
-Xnoclassgc 禁用垃圾回收
-XX:SoftRefLRUPolicyMSPerMB 每兆堆空閒空間中SoftReference的存活時間 1s softly reachable objects will remain alive for some amount of time after the last time they were referenced. The default value is one second of lifetime per free megabyte in the heap
-XX:PretenureSizeThreshold 物件超過多大是直接在舊生代分配 0 單位位元組 新生代採用Parallel Scavenge GC時無效 另一種直接在舊生代分配的情況是大的陣列物件,且陣列中無外部引用物件.
-XX:TLABWasteTargetPercent TLAB佔eden區的百分比 1%
-XX:+CollectGen0First FullGC時是否先YGC false

並行收集器相關引數

引數名稱 含義 預設值 解釋說明
-XX:+UseParallelGC Full GC採用parallel MSC (此項待驗證) 選擇垃圾收集器為並行收集器.此配置僅對年輕代有效.即上述配置下,年輕代使用併發收集,而年老代仍舊使用序列收集.(此項待驗證)
-XX:+UseParNewGC 設定年輕代為並行收集 可與CMS收集同時使用 JDK5.0以上,JVM會根據系統配置自行設定,所以無需再設定此值
-XX:ParallelGCThreads 並行收集器的執行緒數 此值最好配置與處理器數目相等 同樣適用於CMS
-XX:+UseParallelOldGC 年老代垃圾收集方式為並行收集(Parallel Compacting) 這個是JAVA 6出現的引數選項
-XX:MaxGCPauseMillis 每次年輕代垃圾回收的最長時間(最大暫停時間) 如果無法滿足此時間,JVM會自動調整年輕代大小,以滿足此值.
-XX:+UseAdaptiveSizePolicy 自動選擇年輕代區大小和相應的Survivor區比例 設定此選項後,並行收集器會自動選擇年輕代區大小和相應的Survivor區比例,以達到目標系統規定的最低相應時間或者收集頻率等,此值建議使用並行收集器時,一直開啟.
-XX:GCTimeRatio 設定垃圾回收時間佔程式執行時間的百分比 公式為1/(1+n)
-XX:+ScavengeBeforeFullGC Full GC前呼叫YGC true Do young generation GC prior to a full GC. (Introduced in 1.4.1.)

CMS處理器引數設定

引數名稱 含義 預設值 解釋說明
-XX:+UseConcMarkSweepGC 使用CMS記憶體收集 測試中配置這個以後,-XX:NewRatio=4的配置失效了,原因不明.所以,此時年輕代大小最好用-Xmn設定.???
-XX:+AggressiveHeap 試圖是使用大量的實體記憶體 長時間大記憶體使用的優化,能檢查計算資源(記憶體, 處理器數量) 至少需要256MB記憶體 大量的CPU/記憶體, (在1.4.1在4CPU的機器上已經顯示有提升)
-XX:CMSFullGCsBeforeCompaction 多少次後進行記憶體壓縮 由於併發收集器不對記憶體空間進行壓縮,整理,所以執行一段時間以後會產生"碎片",使得執行效率降低.此值設定執行多少次GC以後對記憶體空間進行壓縮,整理.
-XX:+CMSParallelRemarkEnabled 降低標記停頓
-XX+UseCMSCompactAtFullCollection 在FULL GC的時候, 對年老代的壓縮 CMS是不會移動記憶體的, 因此, 這個非常容易產生碎片, 導致記憶體不夠用, 因此, 記憶體的壓縮這個時候就會被啟用。 增加這個引數是個好習慣。 可能會影響效能,但是可以消除碎片
-XX:+UseCMSInitiatingOccupancyOnly 使用手動定義初始化定義開始CMS收集 禁止hostspot自行觸發CMS GC
-XX:CMSInitiatingOccupancyFraction=70 使用cms作為垃圾回收 使用70%後開始CMS收集 92 為了保證不出現promotion failed(見下面介紹)錯誤,該值的設定需要滿足以下公式CMSInitiatingOccupancyFraction計算公式
-XX:CMSInitiatingPermOccupancyFraction 設定Perm Gen使用到達多少比率時觸發 92
-XX:+CMSIncrementalMode 設定為增量模式 用於單CPU情況
-XX:+CMSClassUnloadingEnabled

JVM輔助資訊引數設定

引數名稱 含義 預設值 解釋說明
-XX:+PrintGC 輸出形式:[GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC 121376K->10414K(130112K), 0.0650971 secs]
-XX:+PrintGCDetails 輸出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]
-XX:+PrintGCTimeStamps
-XX:+PrintGC:PrintGCTimeStamps 可與-XX:+PrintGC -XX:+PrintGCDetails混合使用 輸出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]
-XX:+PrintGCApplicationStoppedTime 列印垃圾回收期間程式暫停的時間.可與上面混合使用 輸出形式:Total time for which application threads were stopped: 0.0468229 seconds
-XX:+PrintGCApplicationConcurrentTime 列印每次垃圾回收前,程式未中斷的執行時間.可與上面混合使用 輸出形式:Application time: 0.5291524 seconds
-XX:+PrintHeapAtGC 列印GC前後的詳細堆疊資訊
-Xloggc:filename 把相關日誌資訊記錄到檔案以便分析. 與上面幾個配合使用
-XX:+PrintClassHistogram garbage collects before printing the histogram.
-XX:+PrintTLAB 檢視TLAB空間的使用情況
XX:+PrintTenuringDistribution 檢視每次minor GC後新的存活週期的閾值 Desired survivor size 1048576 bytes, new threshold 7 (max 15) new threshold 7即標識新的存活週期的閾值為7。

JVM GC垃圾回收器引數設定

JVM給出了3種選擇:序列收集器並行收集器併發收集器。序列收集器只適用於小資料量的情況,所以生產環境的選擇主要是並行收集器和併發收集器。預設情況下JDK5.0以前都是使用序列收集器,如果想使用其他收集器需要在啟動時加入相應引數。JDK5.0以後,JVM會根據當前系統配置進行智慧判斷。

序列收集器
-XX:+UseSerialGC:設定序列收集器。

並行收集器(吞吐量優先)
-XX:+UseParallelGC:設定為並行收集器。此配置僅對年輕代有效。即年輕代使用並行收集,而年老代仍使用序列收集。

-XX:ParallelGCThreads=20:配置並行收集器的執行緒數,即:同時有多少個執行緒一起進行垃圾回收。此值建議配置與CPU數目相等。

-XX:+UseParallelOldGC:配置年老代垃圾收集方式為並行收集。JDK6.0開始支援對年老代並行收集。

-XX:MaxGCPauseMillis=100:設定每次年輕代垃圾回收的最長時間(單位毫秒)。如果無法滿足此時間,JVM會自動調整年輕代大小,以滿足此時間。

-XX:+UseAdaptiveSizePolicy:設定此選項後,並行收集器會自動調整年輕代Eden區大小和Survivor區大小的比例,以達成目標系統規定的最低響應時間或者收集頻率等指標。此引數建議在使用並行收集器時,一直開啟。
併發收集器(響應時間優先)

並行收集器

-XX:+UseConcMarkSweepGC:即CMS收集,設定年老代為併發收集。CMS收集是JDK1.4後期版本開始引入的新GC演算法。它的主要適合場景是對響應時間的重要性需求大於對吞吐量的需求,能夠承受垃圾回收執行緒和應用執行緒共享CPU資源,並且應用中存在比較多的長生命週期物件。CMS收集的目標是儘量減少應用的暫停時間,減少Full GC發生的機率,利用和應用程式執行緒併發的垃圾回收執行緒來標記清除年老代記憶體。

-XX:+UseParNewGC:設定年輕代為併發收集。可與CMS收集同時使用。JDK5.0以上,JVM會根據系統配置自行設定,所以無需再設定此引數。

-XX:CMSFullGCsBeforeCompaction=0:由於併發收集器不對記憶體空間進行壓縮和整理,所以執行一段時間並行收集以後會產生記憶體碎片,記憶體使用效率降低。此引數設定執行0次Full GC後對記憶體空間進行壓縮和整理,即每次Full GC後立刻開始壓縮和整理記憶體。

-XX:+UseCMSCompactAtFullCollection:開啟記憶體空間的壓縮和整理,在Full GC後執行。可能會影響效能,但可以消除記憶體碎片。

-XX:+CMSIncrementalMode:設定為增量收集模式。一般適用於單CPU情況。

-XX:CMSInitiatingOccupancyFraction=70:表示年老代記憶體空間使用到70%時就開始執行CMS收集,以確保年老代有足夠的空間接納來自年輕代的物件,避免Full GC的發生。

其它垃圾回收引數

-XX:+ScavengeBeforeFullGC:年輕代GC優於Full GC執行。

-XX:-DisableExplicitGC:不響應 System.gc() 程式碼。

-XX:+UseThreadPriorities:啟用本地執行緒優先順序API。即使 java.lang.Thread.setPriority() 生效,不啟用則無效。

-XX:SoftRefLRUPolicyMSPerMB=0:軟引用物件在最後一次被訪問後能存活0毫秒(JVM預設為1000毫秒)。

-XX:TargetSurvivorRatio=90:允許90%的Survivor區被佔用(JVM預設為50%)。提高對於Survivor區的使用率。

JVM引數優先順序

-Xmn,-XX:NewSize/-XX:MaxNewSize,-XX:NewRatio 3組引數都可以影響年輕代的大小,混合使用的情況下,優先順序是什麼?

答案如下:

高優先順序:-XX:NewSize/-XX:MaxNewSize
中優先順序:-Xmn(預設等效 -Xmn=-XX:NewSize=-XX:MaxNewSize=?)
低優先順序:-XX:NewRatio

推薦使用-Xmn引數,原因是這個引數簡潔,相當於一次設定 NewSize/MaxNewSIze,而且兩者相等,適用於生產環境。-Xmn 配合 -Xms/-Xmx,即可將堆記憶體佈局完成。

-Xmn引數是在JDK 1.4 開始支援。

下面用一些小案例加深理解:

HelloGC是java程式碼編譯後的一個class檔案,程式碼:

public class T01_HelloGC {
    public static void main(String[] args) {

        for(int i=0; i<10000; i++) {
            byte[] b = new byte[1024 * 1024];
        }
    }
}
  1. java -XX:+PrintCommandLineFlags HelloGC

    [root@localhost courage]# java -XX:+PrintCommandLineFlags T01_HelloGC
    -XX:InitialHeapSize=61780800 -XX:MaxHeapSize=988492800 -XX:+PrintCommandLineFlags -XX
    :+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC 
    
  2. java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC  HelloGC
    PrintGCDetails PrintGCTimeStamps PrintGCCauses
    

    結果:

    -XX:InitialHeapSize=41943040 -XX:MaxHeapSize=62914560 -XX:MaxNewSize=10485760 -XX:NewSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGC -XX:+UseCompressedClassPointers -XX:+UseCompressedOops 
    -XX:+UseParallelGC[GC (Allocation Failure)  7839K->392K(39936K), 0.0015452 secs]
    [GC (Allocation Failure)  7720K->336K(39936K), 0.0005439 secs]
    [GC (Allocation Failure)  7656K->336K(39936K), 0.0005749 secs]
    [GC (Allocation Failure)  7659K->368K(39936K), 0.0005095 secs]
    [GC (Allocation Failure)  7693K->336K(39936K), 0.0004385 secs]
    [GC (Allocation Failure)  7662K->304K(40448K), 0.0028468 secs]
    ......
    

    命令解釋:

    java:表示使用java執行器執行
    -Xmn10M :表示設定年輕代值為10M
    -Xms40M :表示設定堆記憶體的最小Heap值為40M
    -Xmx60M :表示設定堆記憶體的最大Heap值為60M
    -XX:+PrintCommandLineFlags:列印顯式隱式引數,就是結果前三行
    -XX:+PrintGC : 列印垃圾回收有關資訊
    HelloGC :這是需要執行的啟動類
    PrintGCDetails :列印GC詳細資訊
    PrintGCTimeStamps :列印GC時間戳
    PrintGCCauses :列印GC產生的原因

    結果解釋:

  1. java -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags HelloGC

    表示使用CMS垃圾收集器,同時列印引數
    列印結果:

    -XX:InitialHeapSize=61780800 
    -XX:MaxHeapSize=988492800 
    -XX:MaxNewSize=329252864 
    -XX:MaxTenuringThreshold=6 
    -XX:OldPLABSize=16 
    -XX:+PrintCommandLineFlags 
    -XX:+UseCompressedClassPointers 
    -XX:+UseCompressedOops 
    -XX:+UseConcMarkSweepGC 
    -XX:+UseParNewGC
    
  2. java -XX:+PrintFlagsInitial 預設引數值

  3. java -XX:+PrintFlagsFinal 最終引數值

  4. java -XX:+PrintFlagsFinal | grep xxx 找到對應的引數

  5. java -XX:+PrintFlagsFinal -version |grep GC

JVM調優流程

JVM調優,設計到三個大的方面,在伺服器出現問題之前要先根據業務場景選擇合適的垃圾處理器,設定不同的虛擬機器引數,執行中觀察GC日誌,分析效能,分析問題定位問題,虛擬機器排錯等內容,如果伺服器掛掉了,要及時生成日誌檔案便於找到問題所在。

調優前的基礎概念

目前的垃圾處理器中,一類是以吞吐量優先,一類是以響應時間優先:

\[吞吐量 = \frac{使用者程式碼執行時間}{使用者程式碼執行時間+垃圾回收執行時間} \]

響應時間:STW越短,響應時間越好

對吞吐量、響應時間、QPS、併發數相關概念可以參考:吞吐量(TPS)、QPS、併發數、響應時間(RT)概念

所謂調優,首先確定追求什麼,是吞吐量? 還是追求響應時間?還是在滿足一定的響應時間的情況下,要求達到多大的吞吐量,等等。一般情況下追求吞吐量的有以下領域:科學計算、資料探勘等。吞吐量優先的垃圾處理器組合一般為:Parallel Scavenge + Parallel Old (PS + PO)。

而追求響應時間的業務有:網站相關 (JDK 1.8之後 G1,之前可以ParNew + CMS + Serial Old)

什麼是調優?

  1. 根據需求進行JVM規劃和預調優
  2. 優化執行JVM執行環境(慢,卡頓)
  3. 解決JVM執行過程中出現的各種問題(OOM)

調優之前的規劃

  • 調優,從業務場景開始,沒有業務場景的調優都是耍流氓

  • 無監控(壓力測試,能看到結果),不調優

  • 步驟:

    1. 熟悉業務場景(沒有最好的垃圾回收器,只有最合適的垃圾回收器)

      1. 響應時間、停頓時間 [CMS G1 ZGC] (需要給使用者作響應)
      2. 吞吐量 = 使用者時間 /( 使用者時間 + GC時間) [PS+PO]
    2. 選擇回收器組合

    3. 計算記憶體需求(經驗值 1.5G 16G)

    4. 選定CPU(越高越好)

    5. 設定年代大小、升級年齡

    6. 設定日誌引數

      1. -Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log 
        -XX:+UseGCLogFileRotation 
        -XX:NumberOfGCLogFiles=5 
        -XX:GCLogFileSize=20M 
        -XX:+PrintGCDetails 
        -XX:+PrintGCDateStamps 
        -XX:+PrintGCCause
        

        日誌引數解釋說明:

        /opt/xxx/logs/xxx-xxx-gc-%t.log 中XXX表示路徑,%t表示時間戳,意思是給日誌檔案新增一個時間標記,如果不新增的話,也就意味著每次虛擬機器啟動都會使用原來的日誌名,那麼會被重寫。

        Rotation中文意思是迴圈、輪流,意味著這個GC日誌會迴圈寫

        GCLogFileSize=20M 指定一個日誌大小為20M,太大了不利於分析,太小又會產生過多的日誌檔案

        NumberOfGCLogFiles=5 : 指定生成的日誌數目

        PrintGCDateStamps :PrintGCDateStamps會列印具體的時間,而PrintGCTimeStamps

        ​ 主要列印針對JVM啟動的時候的相對時間,相對來說前者更消耗記憶體。

      2. 或者每天產生一個日誌檔案

    7. 觀察日誌情況
      日誌有分析工具,視覺化分析工具有GCeasyGCViewer

CPU高負荷排查流程

  1. 系統CPU經常100%,如何調優?(面試高頻) CPU100%那麼一定有執行緒在佔用系統資源,
    1. 找出哪個程式cpu高(top)
    2. 該程式中的哪個執行緒cpu高(top -Hp)
    3. 匯出該執行緒的堆疊 (jstack)
    4. 查詢哪個方法(棧幀)消耗時間 (jstack)
    5. 工作執行緒佔比高 | 垃圾回收執行緒佔比高
  2. 系統記憶體飆高,如何查詢問題?(面試高頻)
    1. 匯出堆記憶體 (jmap)
    2. 分析 (jhat jvisualvm mat jprofiler ... )
  3. 如何監控JVM
    1. jstat jvisualvm jprofiler arthas top...

CPU高負荷排查案例

  1. 測試程式碼:

    import java.math.BigDecimal;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    import java.util.concurrent.ScheduledThreadPoolExecutor;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * 從資料庫中讀取信用資料,套用模型,並把結果進行記錄和傳輸
     */
    
    public class T15_FullGC_Problem01 {
    
        private static class CardInfo {
            BigDecimal price = new BigDecimal(0.0);
            String name = "張三";
            int age = 5;
            Date birthdate = new Date();
    
            public void m() {}
        }
    
        private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50,
                new ThreadPoolExecutor.DiscardOldestPolicy());
    
        public static void main(String[] args) throws Exception {
            executor.setMaximumPoolSize(50);
    
            for (;;){
                modelFit();
                Thread.sleep(100);
            }
        }
    
        private static void modelFit(){
            List<CardInfo> taskList = getAllCardInfo();
            taskList.forEach(info -> {
                // do something
                executor.scheduleWithFixedDelay(() -> {
                    //do sth with info
                    info.m();
    
                }, 2, 3, TimeUnit.SECONDS);
            });
        }
    
        private static List<CardInfo> getAllCardInfo(){
            List<CardInfo> taskList = new ArrayList<>();
    
            for (int i = 0; i < 100; i++) {
                CardInfo ci = new CardInfo();
                taskList.add(ci);
            }
    
            return taskList;
        }
    }
    
  2. java -Xms200M -Xmx200M -XX:+PrintGC com.courage.jvm.gc.T15_FullGC_Problem01

  3. 收到CPU報警資訊(CPU Memory)

  4. top命令觀察到問題:記憶體不斷增長 CPU佔用率居高不下

    [root@localhost ~]# top
    top - 22:03:18 up 40 min,  5 users,  load average: 0.09, 0.16, 0.34
    Tasks: 210 total,   1 running, 209 sleeping,   0 stopped,   0 zombie
    %Cpu(s):  0.2 us,  3.0 sy,  0.0 ni, 96.8 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    KiB Mem :  3861300 total,  2355260 free,   904588 used,   601452 buff/cache
    KiB Swap:  4063228 total,  4063228 free,        0 used.  2716336 avail Mem 
    
       PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                 
      3751 root      20   0 3780976  93864  11816 S  42.2  2.4   0:21.00 java
      1868 mysql     20   0 1907600 357452  14744 S   0.7  9.3   0:17.40 mysqld
      3816 root      20   0  162124   2352   1580 R   0.3  0.1   0:00.12 top
    
  5. top -Hp 觀察程式中的執行緒,哪個執行緒CPU和記憶體佔比高

    [root@localhost ~]# top -Hp 3751
    top - 22:03:15 up 40 min,  5 users,  load average: 0.09, 0.16, 0.34
    Threads:  66 total,   0 running,  66 sleeping,   0 stopped,   0 zombie
    %Cpu(s):  0.0 us,  2.5 sy,  0.0 ni, 97.5 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    KiB Mem :  3861300 total,  2354800 free,   905048 used,   601452 buff/cache
    KiB Swap:  4063228 total,  4063228 free,        0 used.  2715876 avail Mem 
    
       PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND              
      3801 root      20   0 3780976  93864  11816 S  1.3  2.4   0:00.40 java
      3766 root      20   0 3780976  93864  11816 S  1.0  2.4   0:00.37 java
      3768 root      20   0 3780976  93864  11816 S  1.0  2.4   0:00.36 java
      3770 root      20   0 3780976  93864  11816 S  1.0  2.4   0:00.39 java
    
  6. jps定位具體java程式,jstack 定位執行緒狀況

    [root@localhost ~]# jstack 3751
    2021-02-07 22:03:03
    Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.271-b09 mixed mode):
    
    "Attach Listener" #59 daemon prio=9 os_prio=0 tid=0x00007f66bc002800 nid=0xf10 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "pool-1-thread-50" #58 prio=5 os_prio=0 tid=0x00007f66fc1de800 nid=0xee7 waiting on condition [0x00007f66e4ecd000]
       java.lang.Thread.State: WAITING (parking)
    	at sun.misc.Unsafe.park(Native Method)
    	- parking to wait for  <0x00000000ff0083a0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
    ......
    

    需要注意的是,jstacktop -Hp Port匯出的棧埠號存在十六進位制轉換關係,例如jstack匯出的" nid=0xf10 "對應"3801"。
    對於上面列印的資訊,重點關注跟Waiting有關的,看看在等待什麼,例如:

    WAITING BLOCKED eg. waiting on <0x0000000088ca3310> (a java.lang.Object) 
    

    假如有一個程式中100個執行緒,很多執行緒都在waiting on ,一定要找到是哪個執行緒持有這把鎖,怎麼找?搜尋jstack dump的資訊,看哪個執行緒持有這把鎖RUNNABLE。

    如果僅僅是看JAVA執行緒,可以使用jps命令重點關注:

    [root@localhost ~]# jps
    4818 Jps
    4746 T15_FullGC_Problem01
    
  7. 為什麼阿里規範裡規定,執行緒的名稱(尤其是執行緒池)都要寫有意義的名稱 怎麼樣自定義執行緒池裡的執行緒名稱?(自定義ThreadFactory)




  1. jinfo pid 程式詳細資訊

    [root@localhost ~]# jinfo 6741
    Attaching to process ID 6741, please wait...
    Debugger attached successfully.
    Server compiler detected.
    JVM version is 25.271-b09
    Java System Properties:
    
    java.runtime.name = Java(TM) SE Runtime Environment
    java.vm.version = 25.271-b09
    sun.boot.library.path = /usr/local/java/jdk1.8.0_271/jre/lib/amd64
    java.vendor.url = http://java.oracle.com/
    java.vm.vendor = Oracle Corporation
    path.separator = :
    file.encoding.pkg = sun.io
    java.vm.name = Java HotSpot(TM) 64-Bit Server VM
    sun.os.patch.level = unknown
    sun.java.launcher = SUN_STANDARD
    user.country = CN
    user.dir = /usr/courage/gc/com/courage
    java.vm.specification.name = Java Virtual Machine Specification
    java.runtime.version = 1.8.0_271-b09
    java.awt.graphicsenv = sun.awt.X11GraphicsEnvironment
    os.arch = amd64
    java.endorsed.dirs = /usr/local/java/jdk1.8.0_271/jre/lib/endorsed
    java.io.tmpdir = /tmp
    line.separator = 
    
    java.vm.specification.vendor = Oracle Corporation
    os.name = Linux
    sun.jnu.encoding = UTF-8
    java.library.path = /usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/
    libjava.specification.name = Java Platform API Specification
    java.class.version = 52.0
    sun.management.compiler = HotSpot 64-Bit Tiered Compilers
    os.version = 3.10.0-1127.el7.x86_64
    user.home = /root
    user.timezone = 
    java.awt.printerjob = sun.print.PSPrinterJob
    file.encoding = UTF-8
    java.specification.version = 1.8
    user.name = root
    java.class.path = .
    java.vm.specification.version = 1.8
    sun.arch.data.model = 64
    sun.java.command = T15_FullGC_Problem01
    java.home = /usr/local/java/jdk1.8.0_271/jre
    user.language = zh
    java.specification.vendor = Oracle Corporation
    awt.toolkit = sun.awt.X11.XToolkit
    java.vm.info = mixed mode
    java.version = 1.8.0_271
    java.ext.dirs = /usr/local/java/jdk1.8.0_271/jre/lib/ext:/usr/java/packages/l
    ib/extsun.boot.class.path = /usr/local/java/jdk1.8.0_271/jre/lib/resources.jar:/usr
    /local/java/jdk1.8.0_271/jre/lib/rt.jar:/usr/local/java/jdk1.8.0_271/jre/lib/sunrsasign.jar:/usr/local/java/jdk1.8.0_271/jre/lib/jsse.jar:/usr/local/java/jdk1.8.0_271/jre/lib/jce.jar:/usr/local/java/jdk1.8.0_271/jre/lib/charsets.jar:/usr/local/java/jdk1.8.0_271/jre/lib/jfr.jar:/usr/local/java/jdk1.8.0_271/jre/classesjava.vendor = Oracle Corporation
    file.separator = /
    java.vendor.url.bug = http://bugreport.sun.com/bugreport/
    sun.io.unicode.encoding = UnicodeLittle
    sun.cpu.endian = little
    sun.cpu.isalist = 
    
    VM Flags:
    Non-default VM flags: -XX:CICompilerCount=3 -XX:InitialHeapSize=209715200 -XX
    :MaxHeapSize=209715200 -XX:MaxNewSize=69730304 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=69730304 -XX:OldSize=139984896 -XX:+PrintGC -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC Command line:  -Xms200M -Xmx200M -XX:+PrintGC
    
  2. jstat -gc 動態觀察gc情況 / 閱讀GC日誌發現頻繁GC / arthas觀察 / jconsole/jvisualVM/ Jprofiler(最好用)

    jstat gc 4655 500 : 每500毫秒列印埠4655的GC的情況

  • S0C:第一個倖存區的大小
  • S1C:第二個倖存區的大小
  • S0U:第一個倖存區的使用大小
  • S1U:第二個倖存區的使用大小
  • EC:伊甸園區的大小
  • EU:伊甸園區的使用大小
  • OC:老年代大小
  • OU:老年代使用大小
  • MC:方法區大小
  • MU:方法區使用大小
  • CCSC:壓縮類空間大小
  • CCSU:壓縮類空間使用大小
  • YGC:年輕代垃圾回收次數
  • YGCT:年輕代垃圾回收消耗時間
  • FGC:老年代垃圾回收次數
  • FGCT:老年代垃圾回收消耗時間
  • GCT:垃圾回收消耗總時間

如果面試官問你是怎麼定位OOM問題的?能否用圖形介面(不能!因為圖形介面會影響伺服器效能)
1:已經上線的系統不用圖形介面用什麼?(cmdline arthas)
2:圖形介面到底用在什麼地方?測試!測試的時候進行監控!(壓測觀察)

  1. jmap -histo 6892 | head -10,查詢有多少物件產生

這明顯能看出來是1對應的類創造的例項instances太多了,反過來追蹤程式碼
  1. jmap -dump:format=b,file=xxx pid :

    線上系統,記憶體特別大,jmap執行期間會對程式產生很大影響,甚至卡頓(電商不適合)
    1:設定了引數HeapDump,OOM的時候會自動產生堆轉儲檔案
    2:很多伺服器備份(高可用),停掉這臺伺服器對其他伺服器不影響
    3:線上定位(一般小點兒公司用不到)

    [root@localhost ~]# jmap -dump:format=b,file=2021_2_8.dump 6892
    Dumping heap to /root/2021_2_8.dump ...
    Heap dump file created
    

    dump檔案存放位置:

  2. java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError com.courage.jvm.gc.T15_FullGC_Problem01
    上面的意思是當發生記憶體溢位時自動生成堆轉儲檔案,需要注意的是,如果生成了這個檔案先不要重啟伺服器,將這個檔案儲存好之後再重啟。

  3. 使用MAT / jhat /jvisualvm 進行dump檔案分析

    [root@localhost ~]# jhat -J-Xmx512M 2021_2_8.dump
    

    報錯:


原因是設定的堆最大值太小了,將512M設定成1024M重新啟動即可:

```shell
[root@localhost ~]# jhat -J-Xmx1024M 2021_2_8.dump
Reading from 2021_2_8.dump...
Dump file created Mon Feb 08 09:00:56 CST 2021
Snapshot read, resolving...
Resolving 4609885 objects...
Chasing references, expect 921 dots..........................................................
.........................................................................................Eliminating duplicate references.............................................................
......................................................................................Snapshot resolved.
Started HTTP server on port 7000
Server is ready.
```


瀏覽器輸入請求http://192.168.182.130:7000 即可檢視,拉到最後:找到對應連結 可以使用OQL查詢特定問題物件


其他可以參考:白灰——軟體測試

  1. 最後找到程式碼的問題

JVM調優工具

jconsole遠端連線

  1. 程式啟動加入引數:

    java -Djava.rmi.server.hostname=192.168.182.130 
    -Dcom.sun.management.jmxremote 
    -Dcom.sun.management.jmxremote.port=11111 
    -Dcom.sun.management.jmxremote.authenticate=false 
    -Dcom.sun.management.jmxremote.ssl=false XXX
    
  2. 如果遭遇 Local host name unknown:XXX的錯誤,修改/etc/hosts檔案,把XXX加入進去

    192.168.182.130 basic localhost localhost.localdomain localhost4 localhost4.localdomain4
    ::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
    
  3. 關閉linux防火牆(實戰中應該開啟對應埠)

    service iptables stop
    chkconfig iptables off #永久關閉
    
  4. windows上開啟 jconsole遠端連線 192.168.182.130:11111

jvisualvm遠端連線

這個軟體在JDK8以後版本中移除了,使用的話需要額外下載,並且要在etc/visualvm.conf中修改預設的JDK_Home地址。
參考:使用jvisualvm的jstatd方式遠端監控Java程式

阿里巴巴Arthas

這個直接看官網就行了,純中文:Arthas 使用者文件

JVM調優案例

引數設定之承受海量訪問的動態Web應用

伺服器配置:8 核 CPU, 8G MEM, JDK 1.6.X

引數方案:
-server -Xmx3550m -Xms3550m -Xmn1256m -Xss128k -XX:SurvivorRatio=6 -XX:MaxPermSize=256m -XX:ParallelGCThreads=8 -XX:MaxTenuringThreshold=0 -XX:+UseConcMarkSweepGC

調優說明:
-Xmx 與 -Xms 相同以避免JVM反覆重新申請記憶體。-Xmx 的大小約等於系統記憶體大小的一半,即充分利用系統資源,又給予系統安全執行的空間。
-Xmn1256m 設定年輕代大小為1256MB。此值對系統效能影響較大,Sun官方推薦配置年輕代大小為整個堆的3/8。
-Xss128k 設定較小的執行緒棧以支援建立更多的執行緒,支援海量訪問,並提升系統效能。
-XX:SurvivorRatio=6 設定年輕代中Eden區與Survivor區的比值。系統預設是8,根據經驗設定為6,則2個Survivor區與1個Eden區的比值為2:6,一個Survivor區佔整個年輕代的1/8。
-XX:ParallelGCThreads=8 配置並行收集器的執行緒數,即同時8個執行緒一起進行垃圾回收。此值一般配置為與CPU數目相等。
-XX:MaxTenuringThreshold=0 設定垃圾最大年齡(在年輕代的存活次數)。如果設定為0的話,則年輕代物件不經過Survivor區直接進入年老代。對於年老代比較多的應用,可以提高效率;如果將此值設定為一個較大值,則年輕代物件會在Survivor區進行多次複製,這樣可以增加物件再年輕代的存活時間,增加在年輕代即被回收的概率。根據被海量訪問的動態Web應用之特點,其記憶體要麼被快取起來以減少直接訪問DB,要麼被快速回收以支援高併發海量請求,因此其記憶體物件在年輕代存活多次意義不大,可以直接進入年老代,根據實際應用效果,在這裡設定此值為0。
-XX:+UseConcMarkSweepGC 設定年老代為併發收集。CMS(ConcMarkSweepGC)收集的目標是儘量減少應用的暫停時間,減少Full GC發生的機率,利用和應用程式執行緒併發的垃圾回收執行緒來標記清除年老代記憶體,適用於應用中存在比較多的長生命週期物件的情況。

引數設定之內部整合構建伺服器

高效能資料處理的工具應用
伺服器配置:1 核 CPU, 4G MEM, JDK 1.6.X
引數方案:
-server -XX:PermSize=196m -XX:MaxPermSize=196m -Xmn320m -Xms768m -Xmx1024m
調優說明:
-XX:PermSize=196m -XX:MaxPermSize=196m 根據整合構建的特點,大規模的系統編譯可能需要載入大量的Java類到記憶體中,所以預先分配好大量的持久代記憶體是高效和必要的。
-Xmn320m 遵循年輕代大小為整個堆的3/8原則。
-Xms768m -Xmx1024m 根據系統大致能夠承受的堆記憶體大小設定即可。

案例分析之XXX

分析中,年後再梳理......

問題定位之XXX

分析中,年後再梳理......

虛擬機器排錯之XXX

分析中,年後再梳理......

相關文章