【深入理解JVM】8、JVM實戰調優+GC演算法+JVM調優如何定位問題+常見的定位JVM優化命令【面試必備】

Hello-zhou發表於2020-11-10

JVM調優第一步,瞭解JVM常用命令列引數

試驗用程式:

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
    1. -Xmn10M:新生代大小
    2. -Xms40M -Xmx60M(一般設定一樣的大小,為了避免不斷的壓縮,浪費系統資源)
    3. 常見引數PrintGCDetails:列印詳細/PrintGCTimeStamps:詳細時間/PrintGCTimeCauses:原因。
  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

PS GC日誌詳解

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

PS日誌格式:

heap dump部分:

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

 

total = eden + 1個survivor(為什麼是一個,只能用一個嗎?)

調優前的基礎概念:

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

所謂調優,首先確定,追求啥?吞吐量優先,還是響應時間優先?還是在滿足一定的響應時間的情況下,要求達到多大的吞吐量...

問題:

科學計算,吞吐量。資料探勘,thrput。吞吐量優先的一般:(PS + PO)

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

什麼是調優?

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

調優,從規劃開始(預規劃)

一般說的併發都是transaction,

QPS:

TPS:

PPS:

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

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

  • 步驟:

    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. 測試程式碼:

  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 PID| head -20,查詢有多少物件產生(這個也有點影響,影響很小,可以用來線上定位)

  11. jmap -dump:format=b,file=xxx pid :

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

    1. 設定了引數HeapDump,OOM的時候會自動產生堆轉儲檔案(也不是特別好)

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

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

  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
    -Xms200M -Xmx200M -XX:+PrintGC com.mashibing.jvm.gc.T15_FullGC_Problem01
  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(記憶體碎片)嚴重的問題。最終還是用Serial Old

    -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction 預設為0 指的是經過多少次FGC才進行壓縮

  2. Floating Garbage(浮動垃圾)最終還是由Serial Old清理。

    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

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

    定義:CMS用的mark-sweep就是老年化產生了很多碎片空間,一但新生代產生的很多物件找不到空間了,就是promotionFailed,找不到空間了就把Serial Old請出來,讓他用一個執行緒在裡面做標記壓縮。

    –XX:CMSInitiatingOccupancyFraction 92%(這個值1.8預設值從68%改成了92%,到達值時就會進行FGC) 可以降低這個值-->68%,讓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%

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 直接記憶體

參考資料

  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

相關文章