天天看點

GC和GC TuningGC和GC Tuning

GC和GC Tuning

作者:馬士兵教育 http://mashibing.com

GC的基礎知識

1.什麼是垃圾

C語言申請記憶體:malloc free

C++: new delete

c/C++ 手動回收記憶體

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都是使用邏輯分代模型

    G1是邏輯分代,實體不分代

    除此之外不僅邏輯分代,而且實體分代

  2. 新生代 + 老年代 + 永久代(1.7)Perm Generation/ 中繼資料區(1.8) Metaspace
    1. 永久代 中繼資料 - Class
    2. 永久代必須指定大小限制 ,中繼資料可以設定,也可以不設定,無上限(受限于實體記憶體)
    3. 字元串常量 1.7 - 永久代,1.8 - 堆
    4. MethodArea邏輯概念 - 永久代、中繼資料
  3. 新生代 = Eden + 2個suvivor區
    1. YGC回收之後,大多數的對象會被回收,活着的進入s0
    2. 再次YGC,活着的對象eden + s0 -> s1
    3. 再次YGC,eden + s1 -> s0
    4. 年齡足夠 -> 老年代 (15 CMS 6)
    5. s區裝不下 -> 老年代
  4. 老年代
    1. 頑固分子
    2. 老年代滿了FGC Full GC
  5. GC Tuning (Generation)
    1. 盡量減少FGC
    2. MinorGC = YGC
    3. MajorGC = FGC
  6. 對象配置設定過程圖

    [外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-Mm6QMWGX-1593487839559)(對象配置設定過程詳解.png)]

  7. 動态年齡:(不重要)

    https://www.jianshu.com/p/989d3b06a49d

  8. 配置設定擔保:(不重要)

    YGC期間 survivor區空間不夠了 空間擔保直接進入老年代

    參考:https://cloud.tencent.com/developer/article/1082730

5.常見的垃圾回收器

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-bhj7OABU-1593487839562)(常用垃圾回收器.png)]

  1. 垃圾回收器的發展路線,是随着記憶體越來越大的過程而演進

    從分代算法演化到不分代算法

    Serial算法 幾十兆

    Parallel算法 幾個G

    CMS 幾十個G - 承上啟下,開始并發回收 -

    .- 三色标記 -

  2. JDK誕生 Serial追随 提高效率,誕生了PS,為了配合CMS,誕生了PN,CMS是1.4版本後期引入,CMS是裡程碑式的GC,它開啟了并發回收的過程,但是CMS毛病較多,是以目前任何一個JDK版本預設是CMS

    并發垃圾回收是因為無法忍受STW

  3. Serial 年輕代 串行回收
  4. PS 年輕代 并行回收
  5. ParNew 年輕代 配合CMS的并行回收
  6. SerialOld
  7. ParallelOld
  8. ConcurrentMarkSweep 老年代 并發的, 垃圾回收和應用程式同時運作,降低STW的時間(200ms)

    CMS問題比較多,是以現在沒有一個版本預設是CMS,隻能手工指定

    CMS既然是MarkSweep,就一定會有碎片化的問題,碎片到達一定程度,CMS的老年代配置設定對象配置設定不下的時候,使用SerialOld 進行老年代回收

    想象一下:

    PS + PO -> 加記憶體 換垃圾回收器 -> PN + CMS + SerialOld(幾個小時 - 幾天的STW)

    幾十個G的記憶體,單線程回收 -> G1 + FGC 幾十個G -> 上T記憶體的伺服器 ZGC

    算法:三色标記 + Incremental Update

  9. G1(200ms - 10ms)

    算法:三色标記 + SATB

  10. ZGC (10ms - 1ms) PK C++

    算法:ColoredPointers + LoadBarrier

  11. Shenandoah

    算法:ColoredPointers + WriteBarrier

  12. Eplison
  13. PS 和 PN差別的延伸閱讀:

    ▪https://docs.oracle.com/en/java/javase/13/gctuning/ergonomics.html#GUID-3D0BB91E-9BFF-4EBB-B523-14493A860E73

  14. 垃圾收集器跟記憶體大小的關系
    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常用指令行參數

  • JVM的指令行參數參考:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
  • HotSpot參數分類

    标準: - 開頭,所有的HotSpot都支援

    非标準:-X 開頭,特定版本HotSpot支援特定指令

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

    java -version

    java -X

    java -XX:+PrintFlagsWithComments //隻有debug版本能用

    試驗用程式:

    import java.util.List;
    import java.util.LinkedList;
    
    public class HelloGC {
      public static void main(String[] args) {
        System.out.println("HelloGC!");
        List list = new LinkedList();
        for(;;) {
          byte[] b = new byte[1024*1024];
          list.add(b);
      }
      }
               

}

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 GC
9. java -XX:+PrintFlagsFinal -version | wc -l 
   共728個參數

### PS GC日志詳解

每種垃圾回收器的日志格式是不同的!

PS日志格式

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-bO1n3WHn-1593487839564)(./GC日志詳解.png)]

heap dump部分:

```java
eden space 5632K, 94% used [0x00000000ff980000,0x00000000ffeb3e28,0x00000000fff00000)
                          後面的記憶體位址指的是,起始位址,使用空間結束位址,整體空間結束位址
           

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-NmrdC6Jl-1593487839567)(GCHeapDump.png)]

total = eden + 1個survivor

調優前的基礎概念:

  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:垂直電商,最高每日百萬訂單,處理訂單系統需要什麼樣的伺服器配置?

    這個問題比較業餘,因為很多不同的伺服器配置都能支撐(1.5G 16G)

    1小時360000集中時間段, 100個訂單/秒,(找一小時内的高峰期,1000訂單/秒)

    經驗值,

    非要計算:一個訂單産生需要多少記憶體?512K * 1000 500M記憶體

    專業一點兒問法:要求響應時間100ms

    壓測!

  • 案例2:12306遭遇春節大規模搶票應該如何支撐?

    12306應該是中國并發量最大的秒殺網站:

    号稱并發量100W最高

    CDN -> LVS -> NGINX -> 業務系統 -> 每台機器1W并發(10K問題) 100台機器

    普通電商訂單 -> 下單 ->訂單系統(IO)減庫存 ->等待使用者付款

    12306的一種可能的模型: 下單 -> 減庫存 和 訂單(redis kafka) 同時異步進行 ->等付款

    減庫存最後還會把壓力壓到一台伺服器

    可以做分布式本地庫存 + 單獨伺服器做庫存均衡

    大流量的處理方法:分而治之

  • 怎麼得到一個事務會消耗多少記憶體?
    1. 弄台機器,看能承受多少TPS?是不是達到目标?擴容或調優,讓它達到
    2. 用壓測來确定

優化環境

  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)
    2. 分析 (jhat jvisualvm mat jprofiler … )
  4. 如何監控JVM
    1. jstat jvisualvm jprofiler arthas top…

解決JVM運作中的問題

一個案例了解常用工具

  1. 測試代碼:
    package com.mashibing.jvm.gc;
    
    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.mashibing.jvm.gc.T15_FullGC_Problem01
  3. 一般是運維團隊首先受到報警資訊(CPU Memory)
  4. top指令觀察到問題:記憶體不斷增長 CPU占用率居高不下
  5. top -Hp 觀察程序中的線程,哪個線程CPU和記憶體占比高
  6. jps定位具體java程序

    jstack 定位線程狀況,重點關注:WAITING BLOCKED

    eg.

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

    假如有一個程序中100個線程,很多線程都在waiting on ,一定要找到是哪個線程持有這把鎖

    怎麼找?搜尋jstack dump的資訊,找 ,看哪個線程持有這把鎖RUNNABLE

    作業:1:寫一個死鎖程式,用jstack觀察 2 :寫一個程式,一個線程持有鎖不釋放,其他線程等待

  7. 為什麼阿裡規範裡規定,線程的名稱(尤其是線程池)都要寫有意義的名稱

    怎麼樣自定義線程池裡的線程名稱?(自定義ThreadFactory)

  8. jinfo pid
  9. jstat -gc 動态觀察gc情況 / 閱讀GC日志發現頻繁GC / arthas觀察 / jconsole/jvisualVM/ Jprofiler(最好用)

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

    如果面試官問你是怎麼定位OOM問題的?如果你回答用圖形界面(錯誤)

    1:已經上線的系統不用圖形界面用什麼?(cmdline arthas)

    2:圖形界面到底用在什麼地方?測試!測試的時候進行監控!(壓測觀察)

  10. jmap - histo 4655 | head -20,查找有多少對象産生
  11. jmap -dump:format=b,file=xxx pid :

    線上系統,記憶體特别大,jmap執行期間會對程序産生很大影響,甚至卡頓(電商不适合)

    1:設定了參數HeapDump,OOM的時候會自動産生堆轉儲檔案(不是很專業,因為多有監控,記憶體增長就會報警)

    2:很多伺服器備份(高可用),停掉這台伺服器對其他伺服器不影響

    3:線上定位(一般小點兒公司用不到)

    4:在測試環境中壓測(産生類似記憶體增長問題,在堆還不是很大的時候進行轉儲)

  12. java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError com.mashibing.jvm.gc.T15_FullGC_Problem01
  13. 使用MAT / jhat /jvisualvm 進行dump檔案分析

    https://www.cnblogs.com/baihuitestsoftware/articles/6406271.html

    jhat -J-mx512M xxx.dump

    http://192.168.17.11:7000

    拉到最後:找到對應連結

    可以使用OQL查找特定問題對象

  14. 找到代碼的問題

jconsole遠端連接配接

  1. 程式啟動加入參數:
    java -Djava.rmi.server.hostname=192.168.17.11 -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.17.11 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.17.11:11111

jvisualvm遠端連接配接

https://www.cnblogs.com/liugh/p/7620336.html (簡單做法)

jprofiler (收費)

arthas線上排查工具

  • 為什麼需要線上排查?

    在生産上我們經常會碰到一些不好排查的問題,例如線程安全問題,用最簡單的threaddump或者heapdump不好查到問題原因。為了排查這些問題,有時我們會臨時加一些日志,比如在一些關鍵的函數裡列印出入參,然後重新打包釋出,如果打了日志還是沒找到問題,繼續加日志,重新打包釋出。對于上線流程複雜而且稽核比較嚴的公司,從改代碼到上線需要層層的流轉,會大大影響問題排查的進度。

  • jvm觀察jvm資訊
  • thread定位線程問題
  • dashboard 觀察系統情況
  • heapdump + jhat分析
  • jad反編譯

    動态代理生成類的問題定位

    第三方的類(觀察代碼)

    版本問題(确定自己最新送出的版本是不是被使用)

  • redefine 熱替換

    目前有些限制條件:隻能改方法實作(方法已經運作完成),不能改方法名, 不能改屬性

    m() -> mm()

  • sc - search class
  • watch - watch method
  • 沒有包含的功能:jmap

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才進行壓縮

  2. 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保持老年代足夠的空間

CMS日志分析

執行指令:java -Xms20M -Xmx20M -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC com.mashibing.jvm.gc.T15_FullGC_Problem01

[GC (Allocation Failure) [ParNew: 6144K->640K(6144K), 0.0265885 secs] 6585K->2770K(19840K), 0.0268035 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]

ParNew:年輕代收集器

6144->640:收集前後的對比

(6144):整個年輕代容量

6585 -> 2770:整個堆的情況

(19840):整個堆大小

[GC (CMS Initial Mark) [1 CMS-initial-mark: 8511K(13696K)] 9866K(19840K), 0.0040321 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
	//8511 (13696) : 老年代使用(最大)
	//9866 (19840) : 整個堆使用(最大)
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.018/0.018 secs] [Times: user=0.01 sys=0.00, real=0.02 secs] 
	//這裡的時間意義不大,因為是并發執行
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
	//标記Card為Dirty,也稱為Card Marking
[GC (CMS Final Remark) [YG occupancy: 1597 K (6144 K)][Rescan (parallel) , 0.0008396 secs][weak refs processing, 0.0000138 secs][class unloading, 0.0005404 secs][scrub symbol table, 0.0006169 secs][scrub string table, 0.0004903 secs][1 CMS-remark: 8511K(13696K)] 10108K(19840K), 0.0039567 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
	//STW階段,YG occupancy:年輕代占用及容量
	//[Rescan (parallel):STW下的存活對象标記
	//weak refs processing: 弱引用處理
	//class unloading: 解除安裝用不到的class
	//scrub symbol(string) table: 
		//cleaning up symbol and string tables which hold class-level metadata and 
		//internalized string respectively
	//CMS-remark: 8511K(13696K): 階段過後的老年代占用及容量
	//10108K(19840K): 階段過後的堆占用及容量

[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.005/0.005 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
	//标記已經完成,進行并發清理
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
	//重置内部結構,為下次GC做準備
           

G1

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

G1日志詳解

[GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0015790 secs]
//young -> 年輕代 Evacuation-> 複制存活對象 
//initial-mark 混合回收的階段,這裡是YGC混合老年代回收
   [Parallel Time: 1.5 ms, GC Workers: 1] //一個GC線程
      [GC Worker Start (ms):  92635.7]
      [Ext Root Scanning (ms):  1.1]
      [Update RS (ms):  0.0]
         [Processed Buffers:  1]
      [Scan RS (ms):  0.0]
      [Code Root Scanning (ms):  0.0]
      [Object Copy (ms):  0.1]
      [Termination (ms):  0.0]
         [Termination Attempts:  1]
      [GC Worker Other (ms):  0.0]
      [GC Worker Total (ms):  1.2]
      [GC Worker End (ms):  92636.9]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.0 ms]
   [Other: 0.1 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.0 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.0 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
//以下是混合回收其他階段
[GC concurrent-root-region-scan-start]
[GC concurrent-root-region-scan-end, 0.0000078 secs]
[GC concurrent-mark-start]
//無法evacuation,進行FGC
[Full GC (Allocation Failure)  18M->18M(20M), 0.0719656 secs]
   [Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)], [Metaspace: 38
76K->3876K(1056768K)] [Times: user=0.07 sys=0.00, real=0.07 secs]

           

案例彙總

OOM産生的原因多種多樣,有些程式未必産生OOM,不斷FGC(CPU飙高,但記憶體回收特别少) (上面案例)

  1. 硬體更新系統反而卡頓的問題(見上)
  2. 線程池不當運用産生OOM問題(見上)

    不斷的往List裡加對象(實在太LOW)

  3. smile jira問題

    實際系統不斷重新開機

    解決問題 加記憶體 + 更換垃圾回收器 G1

    真正問題在哪兒?不知道

  4. tomcat http-header-size過大問題(Hector)
  5. lambda表達式導緻方法區溢出問題(MethodArea / Perm Metaspace)

    LambdaGC.java -XX:MaxMetaspaceSize=9M -XX:+PrintGCDetails

    "C:\Program Files\Java\jdk1.8.0_181\bin\java.exe" -XX:MaxMetaspaceSize=9M -XX:+PrintGCDetails "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2019.1\lib\idea_rt.jar=49316:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2019.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar;C:\work\ijprojects\JVM\out\production\JVM;C:\work\ijprojects\ObjectSize\out\artifacts\ObjectSize_jar\ObjectSize.jar" com.mashibing.jvm.gc.LambdaGC
    [GC (Metadata GC Threshold) [PSYoungGen: 11341K->1880K(38400K)] 11341K->1888K(125952K), 0.0022190 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [Full GC (Metadata GC Threshold) [PSYoungGen: 1880K->0K(38400K)] [ParOldGen: 8K->1777K(35328K)] 1888K->1777K(73728K), [Metaspace: 8164K->8164K(1056768K)], 0.0100681 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
    [GC (Last ditch collection) [PSYoungGen: 0K->0K(38400K)] 1777K->1777K(73728K), 0.0005698 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [Full GC (Last ditch collection) [PSYoungGen: 0K->0K(38400K)] [ParOldGen: 1777K->1629K(67584K)] 1777K->1629K(105984K), [Metaspace: 8164K->8156K(1056768K)], 0.0124299 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] 
    java.lang.reflect.InvocationTargetException
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:498)
    	at sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:388)
    	at sun.instrument.InstrumentationImpl.loadClassAndCallAgentmain(InstrumentationImpl.java:411)
    Caused by: java.lang.OutOfMemoryError: Compressed class space
    	at sun.misc.Unsafe.defineClass(Native Method)
    	at sun.reflect.ClassDefiner.defineClass(ClassDefiner.java:63)
    	at sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:399)
    	at sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:394)
    	at java.security.AccessController.doPrivileged(Native Method)
    	at sun.reflect.MethodAccessorGenerator.generate(MethodAccessorGenerator.java:393)
    	at sun.reflect.MethodAccessorGenerator.generateSerializationConstructor(MethodAccessorGenerator.java:112)
    	at sun.reflect.ReflectionFactory.generateConstructor(ReflectionFactory.java:398)
    	at sun.reflect.ReflectionFactory.newConstructorForSerialization(ReflectionFactory.java:360)
    	at java.io.ObjectStreamClass.getSerializableConstructor(ObjectStreamClass.java:1574)
    	at java.io.ObjectStreamClass.access$1500(ObjectStreamClass.java:79)
    	at java.io.ObjectStreamClass$3.run(ObjectStreamClass.java:519)
    	at java.io.ObjectStreamClass$3.run(ObjectStreamClass.java:494)
    	at java.security.AccessController.doPrivileged(Native Method)
    	at java.io.ObjectStreamClass.<init>(ObjectStreamClass.java:494)
    	at java.io.ObjectStreamClass.lookup(ObjectStreamClass.java:391)
    	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1134)
    	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
    	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
    	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    	at javax.management.remote.rmi.RMIConnectorServer.encodeJRMPStub(RMIConnectorServer.java:727)
    	at javax.management.remote.rmi.RMIConnectorServer.encodeStub(RMIConnectorServer.java:719)
    	at javax.management.remote.rmi.RMIConnectorServer.encodeStubInAddress(RMIConnectorServer.java:690)
    	at javax.management.remote.rmi.RMIConnectorServer.start(RMIConnectorServer.java:439)
    	at sun.management.jmxremote.ConnectorBootstrap.startLocalConnectorServer(ConnectorBootstrap.java:550)
    	at sun.management.Agent.startLocalManagementAgent(Agent.java:137)
    
               
  6. 直接記憶體溢出問題(少見)

    《深入了解Java虛拟機》P59,使用Unsafe配置設定直接記憶體,或者使用NIO的問題

  7. 棧溢出問題

    -Xss設定太小

  8. 比較一下這兩段程式的異同,分析哪一個是更優的寫法:
    Object o = null;
    for(int i=0; i<100; i++) {
        o = new Object();
        //業務處理
    }
               
    for(int i=0; i<100; i++) {
        Object o = new Object();
    }
               
  9. 重寫finalize引發頻繁GC

    小米雲,HBase同步系統,系統通過nginx通路逾時報警,最後排查,C++程式員重寫finalize引發頻繁GC問題

    為什麼C++程式員會重寫finalize?(new delete)

    finalize耗時比較長(200ms)

  10. 如果有一個系統,記憶體一直消耗不超過10%,但是觀察GC日志,發現FGC總是頻繁産生,會是什麼引起的?

    System.gc() (這個比較Low)

  11. Distuptor有個可以設定鍊的長度,如果過大,然後對象大,消費完不主動釋放,會溢出 (來自 死物風情)
  12. 用jvm都會溢出,mycat用崩過,1.6.5某個臨時版本解析sql子查詢算法有問題,9個exists的聯合sql就導緻生成幾百萬的對象(來自 死物風情)
  13. new 大量線程,會産生 native thread OOM,(low)應該用線程池,

    解決方案:減少堆空間(太TMlow了),預留更多記憶體産生native thread

    JVM記憶體占實體記憶體比例 50% - 80%

  14. 近期學生案例SQLLite的類庫,批處理的時候會把所有的結果加載記憶體,有的人一下子更新幾十萬條資料,結果就産生了記憶體溢出,定位上用的是排除法,去掉這個子產品就沒問題,加上該子產品就會出問題
  15. java線上解壓以及壓縮檔案造成的記憶體溢出
  16. java使用opencv造成的卡頓與緩慢
  17. 最容易引起崩潰的報表系統
  18. 分庫分表所引起的系統崩潰

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. -XX:MaxTenuringThreshold控制的是什麼?

    A: 對象升入老年代的年齡

    B: 老年代觸發FGC時的記憶體垃圾比例

  2. 生産環境中,傾向于将最大堆記憶體和最小堆記憶體設定為:(為什麼?)

    A: 相同 B:不同

  3. JDK1.8預設的垃圾回收器是:

    A: ParNew + CMS

    B: G1

    C: PS + ParallelOld

    D: 以上都不是

  4. 什麼是響應時間優先?
  5. 什麼是吞吐量優先?
  6. ParNew和PS的差別是什麼?
  7. ParNew和ParallelOld的差別是什麼?(年代不同,算法不同)
  8. 長時間計算的場景應該選擇:A:停頓時間 B: 吞吐量
  9. 大規模電商網站應該選擇:A:停頓時間 B: 吞吐量
  10. HotSpot的垃圾收集器最常用有哪些?
  11. 常見的HotSpot垃圾收集器組合有哪些?
  12. JDK1.7 1.8 1.9的預設垃圾回收器是什麼?如何檢視?
  13. 所謂調優,到底是在調什麼?
  14. 如果采用PS + ParrallelOld組合,怎麼做才能讓系統基本不産生FGC
  15. 如果采用ParNew + CMS組合,怎樣做才能夠讓系統基本不産生FGC

    1.加大JVM記憶體

    2.加大Young的比例

    3.提高Y-O的年齡

    4.提高S區比例

    5.避免代碼記憶體洩漏

  16. G1是否分代?G1垃圾回收器會産生FGC嗎?
  17. 如果G1産生FGC,你應該做什麼?
    1. 擴記憶體
    2. 提高CPU性能(回收的快,業務邏輯産生對象的速度固定,垃圾回收越快,記憶體空間越大)
    3. 降低MixedGC觸發的門檻值,讓MixedGC提早發生(預設是45%)
  18. 問:生産環境中能夠随随便便的dump嗎?

    小堆影響不大,大堆會有服務暫停或卡頓(加live可以緩解),dump前會有FGC

  19. 問:常見的OOM問題有哪些?

    棧 堆 MethodArea 直接記憶體

  20. 如果JVM程序靜悄悄退出怎麼辦?
    1. JVM自身OOM導緻
      1. heap dump on oom,這種最容易解決
    2. JVM自身故障
      1. -XX:ErrorFile=/var/log/hs_err_pid.log 超級複雜的檔案 包括:crash線程資訊 safepoint資訊 鎖資訊 native code cache , 編譯事件, gc相關記錄 jvm記憶體映射 等等
    3. 被Linux OOM killer殺死
      1. 日志位于/var/log/messages
      2. egrep -i ‘killed process’ /var/log/messages
    4. 硬體或核心問題
      1. dmesg | grep java
    5. 找我!
  21. 如何排查直接記憶體?
    1. NMT打開 – -XX:NativeMemoryTracking=detail
    2. perf工具
    3. gperftools
  22. 有哪些常用的日志分析工具?
    1. gceasy
  23. CPU暴增如何排查?
    1. top -Hp jstack
    2. arthas - dashboard thread thread XXXX
    3. 兩種情況:1:業務線程 2:GC線程 - GC日志
  24. 死鎖如何排查?
    1. jstack 觀察線程情況
    2. arthas - thread -b

參考資料

  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. https://www.cnblogs.com/nxlhero/p/11660854.html 線上排查工具
  6. https://www.jianshu.com/p/507f7e0cc3a3 arthas常用指令
  7. Arthas手冊:
    1. 啟動arthas java -jar arthas-boot.jar
    2. 綁定java程序
    3. dashboard指令觀察系統整體情況
    4. help 檢視幫助
    5. help xx 檢視具體指令幫助
  8. jmap指令參考: https://www.jianshu.com/p/507f7e0cc3a3
    1. jmap -heap pid
    2. jmap -histo pid
    3. jmap -clstats pid
  9. https://blog.csdn.net/chenssy/article/details/78271744 分析hotspot error file

繼續閱讀