性能測試之垃圾處理器以及JVM調優普及

GC的基礎知識

1.什麼是垃圾

C語言申請內存:malloc freehtml

C++: new deletejava

c/C++ 手動回收內存nginx

Java: new ?程序員

自動內存回收,編程上簡單,系統不容易出錯,手動釋放內存,容易出兩種類型的問題:面試

  1. 忘記回收
  2. 屢次回收

沒有任何引用指向的一個對象或者多個對象(循環引用)算法

2.如何定位垃圾

  1. 引用計數(ReferenceCount)
  2. 根可達算法(RootSearching)

3.常見的垃圾回收算法

  1. 標記清除(mark sweep) - 位置不連續 產生碎片 效率偏低(兩遍掃描)
  2. 拷貝算法 (copying) - 沒有碎片,浪費空間
  3. 標記壓縮(mark compact) - 沒有碎片,效率偏低(兩遍掃描,指針須要調整)

4.JVM內存分代模型(用於分代垃圾回收算法)

  1. 部分垃圾回收器使用的模型

除Epsilon ZGC Shenandoah以外的GC都是使用邏輯分代模型sql

G1是邏輯分代,物理不分代編程

除此以外不只邏輯分代,並且物理分代windows

  1. 新生代 + 老年代 + 永久代(1.7)Perm Generation/ 元數據區(1.8) Metaspacetomcat

    1. 永久代 元數據 - Class
    2. 永久代必須指定大小限制 ,元數據能夠設置,也能夠不設置,無上限(受限於物理內存)
    3. 字符串常量 1.7 - 永久代,1.8 - 堆
    4. MethodArea邏輯概念 - 永久代、元數據
  2. 新生代 = Eden + 2個suvivor區

    1. YGC回收以後,大多數的對象會被回收,活着的進入s0
    2. 再次YGC,活着的對象eden + s0 -> s1
    3. 再次YGC,eden + s1 -> s0
    4. 年齡足夠 -> 老年代 (15 CMS 6)
    5. s區裝不下 -> 老年代
  3. 老年代

    1. 頑固分子
    2. 老年代滿了FGC Full GC
  4. GC Tuning (Generation)

    1. 儘可能減小FGC
    2. MinorGC = YGC
    3. MajorGC = FGC
  5. 對象分配過程圖 

  6. 動態年齡:(不重要) https://www.jianshu.com/p/989d3b06a49d

  7. 分配擔保:(不重要) YGC期間 survivor區空間不夠了 空間擔保直接進入老年代 參考:https://cloud.tencent.com/developer/article/1082730

5.常見的垃圾回收器

  1. JDK誕生 Serial追隨 提升效率,誕生了PS,爲了配合CMS,誕生了PN,CMS是1.4版本後期引入,CMS是里程碑式的GC,它開啓了併發回收的過程,可是CMS毛病較多,所以目前任何一個JDK版本默認是CMS 併發垃圾回收是由於沒法忍受STW
  2. Serial 年輕代 串行回收
  3. PS 年輕代 並行回收
  4. ParNew 年輕代 配合CMS的並行回收
  5. SerialOld
  6. ParallelOld
  7. ConcurrentMarkSweep 老年代 併發的, 垃圾回收和應用程序同時運行,下降STW的時間(200ms) CMS問題比較多,因此如今沒有一個版本默認是CMS,只能手工指定 CMS既然是MarkSweep,就必定會有碎片化的問題,碎片到達必定程度,CMS的老年代分配對象分配不下的時候,使用SerialOld 進行老年代回收 想象一下: PS + PO -> 加內存 換垃圾回收器 -> PN + CMS + SerialOld(幾個小時 - 幾天的STW) 幾十個G的內存,單線程回收 -> G1 + FGC 幾十個G -> 上T內存的服務器 ZGC 算法:三色標記 + Incremental Update
  8. G1(10ms) 算法:三色標記 + SATB
  9. ZGC (1ms) PK C++ 算法:ColoredPointers + LoadBarrier
  10. Shenandoah 算法:ColoredPointers + WriteBarrier
  11. Eplison
  12. PS 和 PN區別的延伸閱讀: ▪https://docs.oracle.com/en/java/javase/13/gctuning/ergonomics.html#GUID-3D0BB91E-9BFF-4EBB-B523-14493A860E73
  13. 垃圾收集器跟內存大小的關係
    1. Serial 幾十兆
    2. PS 上百兆 - 幾個G
    3. CMS - 20G
    4. G1 - 上百G
    5. ZGC - 4T - 16T(JDK13)

1.8默認的垃圾回收:PS + ParallelOld

常見垃圾回收器組合參數設定:(1.8)

  • -XX:+UseSerialGC = Serial New (DefNew) + Serial Old
    • 小型程序。默認狀況下不會是這種選項,HotSpot會根據計算及配置和JDK版本自動選擇收集器
  • -XX:+UseParNewGC = ParNew + SerialOld
    • 這個組合已經不多用(在某些版本中已經廢棄)
    • https://stackoverflow.com/questions/34962257/why-remove-support-for-parnewserialold-anddefnewcms-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

JVM調優第一步,瞭解JVM經常使用命令行參數

  • VM的命令行參數參考:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

  • HotSpot參數分類

標準: - 開頭,全部的HotSpot都支持

非標準:-X 開頭,特定版本HotSpot支持特定命令

不穩定:-XX 開頭,下個版本可能取消

  1. 區分概念:內存泄漏memory leak,內存溢出out of memory
  2. java -XX:+PrintCommandLineFlags HelloGC
  3. java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC HelloGC PrintGCDetails PrintGCTimeStamps PrintGCCauses
  4. java -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags HelloGC
  5. java -XX:+PrintFlagsInitial 默認參數值
  6. java -XX:+PrintFlagsFinal 最終參數值
  7. java -XX:+PrintFlagsFinal | grep xxx 找到對應的參數
  8. java -XX:+PrintFlagsFinal -version |grep 

調優前的基礎概念:

  1. 吞吐量:用戶代碼時間 /(用戶代碼執行時間 + 垃圾回收時間)
  2. 響應時間:STW越短,響應時間越好

所謂調優,首先肯定,追求啥?吞吐量優先,仍是響應時間優先?仍是在知足必定的響應時間的狀況下,要求達到多大的吞吐量...

問題:

科學計算,吞吐量。數據挖掘,thrput。吞吐量優先的通常:(PS + PO)

響應時間:網站 GUI API (1.8 G1)

什麼是調優?

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

調優,從規劃開始

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

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

  • 步驟:

    1. 熟悉業務場景(沒有最好的垃圾回收器,只有最合適的垃圾回收器)
      1. 響應時間、停頓時間 [CMS G1 ZGC] (須要給用戶做響應)
      2. 吞吐量 = 用戶時間 /( 用戶時間 + GC時間) [PS]
    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
      2. 或者天天產生一個日誌文件
    7. 觀察日誌狀況

優化環境

  1. 有一個50萬PV的資料類網站(從磁盤提取文檔到內存)原服務器32位,1.5G 的堆,用戶反饋網站比較緩慢,所以公司決定升級,新的服務器爲64位,16G 的堆內存,結果用戶反饋卡頓十分嚴重,反而比之前效率更低了
    1. 爲何原網站慢? 不少用戶瀏覽數據,不少數據load到內存,內存不足,頻繁GC,STW長,響應時間變慢
    2. 爲何會更卡頓? 內存越大,FGC時間越長
    3. 咋辦? PS -> PN + CMS 或者 G1
  2. 系統CPU常常100%,如何調優?(面試高頻) CPU100%那麼必定有線程在佔用系統資源,
    1. 找出哪一個進程cpu高(top)
    2. 該進程中的哪一個線程cpu高(top -Hp)
    3. 導出該線程的堆棧 (jstack)
    4. 查找哪一個方法(棧幀)消耗時間 (jstack)
    5. 工做線程佔比高 | 垃圾回收線程佔比高
  3. 系統內存飆高,如何查找問題?
    1. 導出堆內存 (jmap/jvisualvm工具)
    2. 使用咱們上一篇講的JVM監控工具裝入文件進行分析
  4. 如何監控JVM
    1. 使用咱們上一篇講的JVM監控工具進行監控

解決JVM運行中的問題

  1. 通常是運維團隊首先受到報警信息(CPU Memory)

  2. top命令觀察到問題:內存不斷增加 CPU佔用率居高不下

  3. top -Hp 觀察進程中的線程,哪一個線程CPU和內存佔比高

  4. jps定位具體java進程 jstack 定位線程情況,重點關注:WAITING BLOCKED eg. waiting on <0x0000000088ca3310> (a java.lang.Object) 假若有一個進程中100個線程,不少線程都在waiting on ,必定要找到是哪一個線程持有這把鎖 怎麼找?搜索jstack dump的信息,找 ,看哪一個線程持有這把鎖RUNNABLE

  5. 使用jvisualvm動態觀察gc狀況,查看YGC以及FGC速度

  6. jmap - histo 端口| head -20,查找有多少對象產生

  7. 線上系統,內存特別大,jmap執行期間會對進程產生很大影響,甚至卡頓(電商不適合) 1:設定了參數HeapDump,OOM的時候會自動產生堆轉儲文件 2:不少服務器備份(高可用),停掉這臺服務器對其餘服務器不影響 3:在線定位(通常小點兒公司用不到)

  8. 使用jvisualvm 進行dump文件分析 

  9. 找到代碼的問題

GC算法的基礎概念

  • Card Table 因爲作YGC時,須要掃描整個OLD區,效率很是低,因此JVM設計了CardTable, 若是一個OLD區CardTable中有對象指向Y區,就將它設爲Dirty,下次掃描時,只須要掃描Dirty Card 在結構上,Card Table用BitMap來實現

CMS

CMS的問題

  1. Memory Fragmentation

-XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction 默認爲0 指的是通過多少次FGC才進行壓縮

  1. Floating Garbage

Concurrent Mode Failure 產生:if the concurrent collector is unable to finish reclaiming the unreachable objects before the tenured generation fills up, or if an allocation cannot be satisfiedwith the available free space blocks in the tenured generation, then theapplication is paused and the collection is completed with all the applicationthreads stopped

解決方案:下降觸發CMS的閾值

PromotionFailed

解決方案相似,保持老年代有足夠的空間

–XX:CMSInitiatingOccupancyFraction 92% 能夠下降這個值,讓CMS保持老年代足夠的空間

G1

  1. ▪https://www.oracle.com/technical-resources/articles/java/g1gc.html

案例彙總

OOM產生的緣由多種多樣,有些程序未必產生OOM,不斷FGC(CPU飆高,但內存回收特別少) (上面案例)

  1. 硬件升級系統反而卡頓的問題

  2. 線程池不當運用產生OOM問題 不斷的往List里加對象(實在太LOW)

  3. smile jira問題 實際系統不斷重啓 解決問題 加內存 + 更換垃圾回收器 G1 真正問題在哪兒?不知道

  4. tomcat http-header-size過大問題(Hector)

  5. lambda表達式致使方法區溢出問題

  1. 重寫finalize引起頻繁GC 小米雲,HBase同步系統,系統經過nginx訪問超時報警,最後排查,C++程序員重寫finalize引起頻繁GC問題 爲何C++程序員會重寫finalize?(new delete) finalize耗時比較長(200ms)

  2. 若是有一個系統,內存一直消耗不超過10%,可是觀察GC日誌,發現FGC老是頻繁產生,會是什麼引發的? System.gc() (這個比較Low)

  3. Distuptor有個能夠設置鏈的長度,若是過大,而後對象大,消費完不主動釋放,會溢出 (來自 死物風情)

  4. 用jvm都會溢出,mycat用崩過,1.6.5某個臨時版本解析sql子查詢算法有問題,9個exists的聯合sql就致使生成幾百萬的對象(來自 死物風情)

  5. new 大量線程,會產生 native thread OOM,(low)應該用線程池, 解決方案:減小堆空間(太TMlow了),預留更多內存產生native thread JVM內存佔物理內存比例 50% - 80%

GC經常使用參數

  • -Xmn -Xms -Xmx -Xss 年輕代 最小堆 最大堆 棧空間
  • -XX:+UseTLAB 使用TLAB,默認打開
  • -XX:+PrintTLAB 打印TLAB的使用狀況
  • -XX:TLABSize 設置TLAB大小
  • -XX:+DisableExplictGC System.gc()無論用 ,FGC
  • -XX:+PrintGC
  • -XX:+PrintGCDetails
  • -XX:+PrintHeapAtGC
  • -XX:+PrintGCTimeStamps
  • -XX:+PrintGCApplicationConcurrentTime (低) 打印應用程序時間
  • -XX:+PrintGCApplicationStoppedTime (低) 打印暫停時長
  • -XX:+PrintReferenceGC (重要性低) 記錄回收了多少種不一樣引用類型的引用
  • -verbose:class 類加載詳細過程
  • -XX:+PrintVMOptions
  • -XX:+PrintFlagsFinal -XX:+PrintFlagsInitial 必須會用
  • -Xloggc:opt/log/gc.log
  • -XX:MaxTenuringThreshold 升代年齡,最大值15
  • 鎖自旋次數 -XX:PreBlockSpin 熱點代碼檢測參數-XX:CompileThreshold 逃逸分析 標量替換 ... 這些不建議設置

Parallel經常使用參數

  • -XX:SurvivorRatio
  • -XX:PreTenureSizeThreshold 大對象到底多大
  • -XX:MaxTenuringThreshold
  • -XX:+ParallelGCThreads 並行收集器的線程數,一樣適用於CMS,通常設爲和CPU核數相同
  • -XX:+UseAdaptiveSizePolicy 自動選擇各區大小比例

CMS經常使用參數

  • -XX:+UseConcMarkSweepGC
  • -XX:ParallelCMSThreads CMS線程數量
  • -XX:CMSInitiatingOccupancyFraction 使用多少比例的老年代後開始CMS收集,默認是68%(近似值),若是頻繁發生SerialOld卡頓,應該調小,(頻繁CMS回收)
  • -XX:+UseCMSCompactAtFullCollection 在FGC時進行壓縮
  • -XX:CMSFullGCsBeforeCompaction 多少次FGC以後進行壓縮
  • -XX:+CMSClassUnloadingEnabled
  • -XX:CMSInitiatingPermOccupancyFraction 達到什麼比例時進行Perm回收
  • GCTimeRatio 設置GC時間佔用程序運行時間的百分比
  • -XX:MaxGCPauseMillis 停頓時間,是一個建議時間,GC會嘗試用各類手段達到這個時間,好比減少年輕代

G1經常使用參數

  • -XX:+UseG1GC
  • -XX:MaxGCPauseMillis 建議值,G1會嘗試調整Young區的塊數來達到這個值
  • -XX:GCPauseIntervalMillis ?GC的間隔時間
  • -XX:+G1HeapRegionSize 分區大小,建議逐漸增大該值,1 2 4 8 16 32。 隨着size增長,垃圾的存活時間更長,GC間隔更長,但每次GC的時間也會更長 ZGC作了改進(動態區塊大小)
  • G1NewSizePercent 新生代最小比例,默認爲5%
  • G1MaxNewSizePercent 新生代最大比例,默認爲60%
  • GCTimeRatio GC時間建議比例,G1會根據這個值調整堆空間
  • ConcGCThreads 線程數量
  • InitiatingHeapOccupancyPercent 啓動G1的堆空間佔用比例

參考資料

  1. https://blogs.oracle.com/jonthecollector/our-collectors
  2. https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
  3. http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp
  4. JVM調優參考文檔:https://docs.oracle.com/en/java/javase/13/gctuning/introduction-garbage-collection-tuning.html#GUID-8A443184-7E07-4B71-9777-4F12947C8184
  5. jmap命令參考: https://www.jianshu.com/p/507f7e0cc3a3
    1. jmap -heap pid
    2. jmap -histo pid
    3. jmap -clstats pid
相關文章
相關標籤/搜索