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


這種文章挺難寫的,一是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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM