JVM筆記10-性能優化之高級特性

一.垃圾回收器配置和 GC 日誌分析

1.堆典型配置:java

32位的操做系統限制堆大小介於1.5G到2G,64位操做系統無限制,同時系統可用虛擬內存和可用物理內存都會限制最大堆的配置。web

堆空間分配典型配置:面試

  1.-Xms:初始堆大小算法

  2.-Xmx:最大堆大小api

  3.-XX:NewSize=n:設置年輕代大小緩存

  4.-XX:NewRatio=n:設置年輕代和年老代的比值。如 n 爲 2,表示年輕代與年老代比值爲 1:2,年輕代佔整個年輕代年老代和的 1/3服務器

  5.-XX:SurvivorRatio=n:年輕代中 Eden 區與兩個 Survivor 區的比值。注意 Survivor 區有兩個。如 n 爲 2,表示 Eden:Survivor=1:2,一個 Survivor 區佔整個年輕代的 1/4併發

  6.-XX:MaxMetaspaceSize=:設置最大化元空間大小app

  7.XX:MetaspaceSize=64m 初始化元空間大小;負載均衡

  8.-Xmn2g:設置年輕代大小爲 2G。整個 JVM 內存大小 = 年輕代大小 + 年老代大小 + 元空間大小。元空間通常固定大小爲 64m,因此增大年輕代後,將會減少年老代大小。此值對系統性能影響較大,Sun 官方推薦配置爲整個堆的 3/8。

  9.-Xss128k:設置每一個線程的棧大小。JDK5.0 之後每一個線程堆棧大小爲 1M,之前每一個線程堆棧大小爲 256K。在相同物理內存下,減少這個值能生成更多的線程。可是操做系統對一個進程內的線程數仍是有限制的在 3000~5000 間。

  10.-XX:SurvivorRatio=4:設置年輕代中 Eden 區與 Survivor 區的大小比值。

  11.-XX:MaxMetaspaceSize=16m:設置元空間大小爲 16m。

  12.-XX:MaxTenuringThreshold=0:設置年輕代最大年齡。若是設置爲 0 的話,則年輕代對象不通過 Survivor 區,直接進入年老代。對於年老代比較多的應用,能夠提升效率。若是將此值設置爲一個較大值,則年輕代對象會在 Survivor 區進行屢次複製,這樣能夠增長對象再年輕代的存活時間,增長在年輕代即被回收的機率。

  13.-XX:+DisableExplicitGC:這個將會忽略手動調用 GC 的代碼使得 System.gc() 的調用就會變成一個空調用,徹底不會觸發任何 GC

-Xmx5120m –Xms5120m -Xmn2g -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxMetaspaceSize=16m -XX:MaxTenuringThreshold=0

2.垃圾回收器配置:

  1.Serial 收集器:

    -XX:MaxTenuringThreshold 默認值是 15,新生代對象晉升爲老年代對象須要通過 15 次 GC。

  2.ParNew 收集器:

    -XX:MaxTenuringThreshold 默認值是 15,新生代對象晉升爲老年代對象須要通過 15 次 GC。

    -XX:UseAdaptiveSizePolicy JVM 根據運行參數,動態調整堆空間大小及晉升年齡值。

  3.Parallel Scavenge/Parallel Old 收集器:

    -XX:ParallelGCThreads 設置併發收集器年輕代收集方式爲並行收集時,使用的CPU數。並行收集線程數。

    -XX:UseAdaptiveSizePolicy JVM根據運行參數,動態調整堆空間大小及晉升年齡值

    -XX:MaxTenuringThreshold 默認值是15,新生代對象晉升爲老年代對象須要通過15次GC

    -XX:GCTimeRatio 設置垃圾回收時間佔程序運行時間的百分比(默認99),公式爲1/(1+n)

    -XX:MaxGCPauseMillis 設置並行收集最大暫停時間。

  4.CMS 收集器:

    -XX:ParallelCMSThreads 垃圾收集器線程數

    -XX:CMSFullGCsBeforeCompaction CMS 採用標記-清理算法,會產生內存碎片,配置執行多少次 FullGC 後對內存進行整理。

    -XX:UseCMSCompactAtFullCollection 配置 FullGC 後是否當即整理內存碎片。

    -XX:CMSInitiatingOccupancyFraction 配置老年代內存使用率達到多少後進行內存回收( JDK6 及以上版本默認值 92%)。

    -XX:CMSInitiatingOccupancyOnly 默認 false,不容許 HostSpot 根據成本自行進行決定什麼時候進行垃圾回收。

    -XX:CMSClassUnloadingEnabled 配置方法區使用 CMS 進行垃圾回收。

    -XX:+CMSIncrementalMode 設置爲增量模式,適用於單 CPU 狀況。

    -XX:+PrintGCApplicationStoppedTime 打印程序 Stop-the-World 的暫停時間。

  5.G1 收集器:

    -XX:G1ReservePercent 默認值 10%,預留的空閒空間的百分比。

    -XX:G1HeapRegionSize 配置 Region 塊的大小,範圍 1MB 到 32MB,設置後會根據最小堆 Java 堆內存劃分出 2048 個 Region 塊。

 

3.垃圾收集統計配置:

   -XX:+PrintGC

   -XX:+PrintGCDetails

   -XX:+PrintGCTimeStamps:可與上面參數一塊兒使用

     -XX:+PrintGCApplicationConcurrentTime:打印每次垃圾回收前,程序未中斷的執行時間,可與上面參數一塊兒使用

   -XX:+PrintGCApplicationStoppedTime:打印垃圾回收期間程序暫停的時間,可與上面參數一塊兒使用

   -XX:PrintHeapAtGC:打印 GC 先後的詳細堆棧信息

   -Xloggc:filename:與上面幾個配合使用,把日誌信息記錄到文件來分析

   經過設定 -XX:+UseG1GC 在整個 Java 堆使用 G1 進行垃圾回收

   經過 -XX:+UseConcMarkSweepGC 設定新生代使用 ParNew(併發複製)收集器,老年代使用 CMS Concurrent Mark-Sweep(併發標記清除)收集器執行內存回收

     經過 -XX:+UseParallelOldGC 手動指定新生代使用 Parallel Scavenge(並行複製)收集器,老年代使用 Parallel Old(並行標記-壓縮)收集器執行內存回收

   經過 -XX:+UseSerialGC 手動指定新生代使用 Serial Coping(串行復制)收集器,老年代使用 Serial Old (串行標記-清理-壓縮)收集器執行內存回收

   經過 -XX:+UseParNewGC 手動指定新生代使用 ParNew(併發複製)收集器,老年代使用 Serial Old (串行標記-清理-壓縮)收集器執行內存回收

   經過 -XX:+UseParallelGC 手動指定新生代使用 Parallel Scavenge(並行複製)收集器,老年代使用 Serial Old (串行標記-清理-壓縮)收集器執行內存回收

 

4.GC日誌分析

/**
 * Created by cong on 2018/8/1.
 *  * 堆溢出
 * 經過run configurations配置下列參數
 * VM Args:-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails
 * -XX:SurvivorRatio=8 -XX:+UseSerialGC
 * -XX:+HeapDumpOnOutOfMemoryError
 * 參數-XX:+HeapDumpOnOutOfMemoryError可讓虛擬機在出現內存溢出異常時Dump出當前的內存堆轉儲快照以便過後進行分析,文件在項目中lib目錄的外層目錄下
 */
public class HeapOutOfMemory {
    static class OutOfMemoryObject {
    }

    public static void main(String[] args) {
        List<OutOfMemoryObject> list = new ArrayList<OutOfMemoryObject>();
        while (true) {
            list.add(new OutOfMemoryObject());
        }
    }
}

在IDE上面配置虛擬機參數,以下:

而後點擊 run,若是報錯,那就是虛擬機參數裏帶了中文標點或者參數名字錯了,使用 Serial Coping(串行復制)/Serial Old (串行標記-清理-壓縮)組合打印的日誌,日誌跟下面的使用

-XX:+UseParallelOldGC 打印的日誌相似,參考下面 Parallel 的日誌分析。

 

-XX:+UseParNewGC 使用 ParNew(併發複製)/Serial Old (串行標記-清理-壓縮),日誌跟下面的使用 -XX:+UseParallelOldGC 打印的日誌相似,參考下面 Parallel 的日誌分析。

 

 -XX:+UseParallelOldGC 使用 Parallel Scavenge(並行複製)/ Parallel Old(並行標記-壓縮) 。日誌分析以下:

[GC [PSYoungGen: 7469K->1016K(9216K)] 7469K->5241K(19456K), 0.0110178 secs] [Times: user=0.05 sys=0.02, real=0.01 secs] 
/*
 * [PSYoungGen: 7469K->1016K(9216K)] PSYoungGen在新生代發生Minor
 * GC,回收前內存佔用7469K,回收後內存佔用1016K,新生代的總內存大小 7469K->5241K(19456K)
 * Java整個堆空間的內存佔用變化,回收前7469K,回收 後5241K,整個堆19456K 0.0110178 secs,整個Minor
 * GC耗時多少秒,[Times: user=0.05 sys=0.02, real=0.01 secs] user程序耗時,sys系統耗時,real實際耗時
 * 多少秒
 */
[GC-- [PSYoungGen: 9208K->9208K(9216K)] 13433K->19440K(19456K), 0.0166318 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 
[Full GC [PSYoungGen: 9208K->0K(9216K)] [ParOldGen: 10232K->10207K(10240K)] 19440K->10207K(19456K) [PSPermGen: 2632K->2631K(21504K)], 0.1698064 secs] [Times: user=0.34 sys=0.00, real=0.17 secs] 
[Full GC [PSYoungGen: 7735K->7728K(9216K)] [ParOldGen: 10207K->8097K(10240K)] 17942K->15826K(19456K) [PSPermGen: 2631K->2631K(21504K)], 0.1632131 secs] [Times: user=0.42 sys=0.00, real=0.15 secs] 
/*
 * Full GC代碼整個堆的GC,PSYoungGen新生代內存使用變化,ParOldGen老年代內存使用變化,PSPermGen永久代內存使用變化,0.
 * 1632131 secs本次Full GC消耗多少秒,Times: user=0.42 sys=0.00, real=0.15 secs 本次Full
 * GC各類時間消耗,全部參數含義跟上面同樣。
 */
[Full GC [PSYoungGen: 7728K->7728K(9216K)] [ParOldGen: 8097K->8089K(10240K)] 15826K->15818K(19456K) [PSPermGen: 2631K->2631K(21504K)], 0.0952587 secs] [Times: user=0.33 sys=0.02, real=0.10 secs] 
java.lang.OutOfMemoryError: Java heap space
// 生成的堆快照文件java_pid26276.hprof,在lib目錄下
Dumping heap to java_pid26276.hprof ...

 

-XX:+UseConcMarkSweepGC 使用ParNew(併發複製)/ CMS Concurrent Mark-Sweep(併發標記清除),日誌以下:

[GC[ParNew: 7469K->1024K(9216K), 0.0339335 secs] 7469K->7079K(19456K), 0.0339810 secs] [Times: user=0.06 sys=0.00, real=0.03 secs] 
// 新生代使用ParNew作垃圾回收,參數含義跟上面分析的同樣
Total time for which application threads were stopped: 0.0341067 seconds
/* 程序暫停時間0.0341067秒,-XX:+PrintGCApplicationStoppedTime打印程序Stop-the-World的暫停時間 */
[GC[ParNew: 9216K->9216K(9216K), 0.0000205 secs][CMS: 6055K->8941K(10240K), 0.0319501 secs] 15271K->13881K(19456K), [CMS Perm : 2630K->2629K(21248K)], 0.0320168 secs] [Times: user=0.03 sys=0.00, real=0.03 secs] 
Total time for which application threads were stopped: 0.0321285 seconds
[GC [1 CMS-initial-mark: 8941K(10240K)] 13881K(19456K), 0.0035077 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
/*CMS初始標記,老年代內存佔用大小8941K,總大小10240K,標記跟根對象集合直接相鏈接的對象的可達性*/
Total time for which application threads were stopped: 0.0035747 seconds
[Full GC[CMS[CMS-concurrent-mark: 0.011/0.011 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
//併發標記,從上次初始標記對象出發,標記垃圾對象和可回收對象
 (concurrent mode failure): 8941K->8940K(10240K), 0.0385219 secs] 14326K->13896K(19456K), [CMS Perm : 2632K->2632K(21248K)], 0.0385659 secs] [Times: user=0.03 sys=0.00, real=0.03 secs] 
/*
 * 併發標記期間,新生成的對象在老年代沒法有足夠的內存容納,產生concurrent mode
 * failure,老年代改用串行收集器,若是不產生concurrent mode
 * failure,後面還有CMS-remark,作最終標記,修正標記,會有暫停時間和內存佔用和總內存,最後就是CMS-cocurrent-sweep,
 * 併發清除,會有清除耗時
 */
Total time for which application threads were stopped: 0.0387397 seconds

接着咱們將上面的例子進行改造,改造以下:

/**
 * Created by cong on 2018/8/1.
 *  * 堆溢出
 * 經過run configurations配置下列參數
 * VM Args:-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails
 * -XX:SurvivorRatio=8 -XX:+UseConcMarkSweepGC
 * -XX:+HeapDumpOnOutOfMemoryError
 * 參數-XX:+HeapDumpOnOutOfMemoryError可讓虛擬機在出現內存溢出異常時Dump出當前的內存堆轉儲快照以便過後進行分析,文件在項目中lib目錄的外層目錄下
 */
public class HeapOutOfMemory {
    static class OutOfMemoryObject {
        public byte[] spaceSize = new byte[1024 * 1024];
        // 產生1024*1024個字節,也就是1024*1KB的內存 ,大小1MB
    }

    public static void creatHeap(int num) throws Exception {
        ArrayList<OutOfMemoryObject> list = new ArrayList<OutOfMemoryObject>();
        for (int i = 0; i < num; i++) {
            list.add(new OutOfMemoryObject());
        }
        System.gc();
    }

    public static void main(String[] args) throws Exception {
        while (true) {
            creatHeap(99);
        }

    }
}

運行結果的,日誌分析以下:

[GC[ParNew: 8111K->8111K(9216K), 0.0000122 secs][CMS: 7176K->9216K(10240K), 0.0104259 secs] 15287K->14839K(19456K), [CMS Perm : 2632K->2632K(21248K)], 0.0104885 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
Total time for which application threads were stopped: 0.0106245 seconds
[GC [1 CMS-initial-mark: 9216K(10240K)] 15863K(19456K), 0.0004189 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Total time for which application threads were stopped: 0.0006342 seconds
[Full GC[CMS[CMS-concurrent-mark: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
 (concurrent mode failure): 9216K->9216K(10240K), 0.0043732 secs] 16947K->16887K(19456K), [CMS Perm : 2632K->2632K(21248K)], 0.0044245 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid1264.hprof ...
Total time for which application threads were stopped: 0.0171680 seconds
Heap dump file created [17857505 bytes in 0.018 secs]
[GC [1 CMS-initial-mark: 9216K(10240K)] 16876K(19456K), 0.0005822 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
Total time for which application threads were stopped: 0.0014073 seconds
[CMS-concurrent-mark: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-preclean: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC[YG occupancy: 7863 K (9216 K)][Rescan (parallel) , 0.0002252 secs][weak refs processing, 0.0000048 secs][scrub string table, 0.0001071 secs] [1 CMS-remark: 9216K(10240K)] 17079K(19456K), 0.0003718 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
/*
 * CMS-remark,作最終標記,修正標記,老年代已用內存9216K,總內存10240K,耗時 0.0003718 
 */
Total time for which application threads were stopped: 0.0005235 seconds
[CMS-concurrent-mark: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
/*
 * CMS-cocurrent-sweep,併發清除耗時0.002秒
 */ 

經過設定 -XX:+UseG1GC 在整個 Java 堆使用 G1 進行垃圾回收,日誌分析以下:

[GC pause (young) (initial-mark), 0.0028719 secs]
   [Parallel Time: 2.2 ms, GC Workers: 4]
      [GC Worker Start (ms): Min: 168.7, Avg: 168.8, Max: 168.9, Diff: 0.2]
      [Ext Root Scanning (ms): Min: 0.9, Avg: 1.1, Max: 1.5, Diff: 0.6, Sum: 4.6]
      [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Processed Buffers: Min: 0, Avg: 2.0, Max: 8, Diff: 8, Sum: 8]
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Object Copy (ms): Min: 0.1, Avg: 0.3, Max: 0.5, Diff: 0.4, Sum: 1.3]
      [Termination (ms): Min: 0.0, Avg: 0.4, Max: 0.5, Diff: 0.5, Sum: 1.4]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
      [GC Worker Total (ms): Min: 1.6, Avg: 1.8, Max: 2.0, Diff: 0.4, Sum: 7.3]
      [GC Worker End (ms): Min: 170.3, Avg: 170.6, Max: 170.8, Diff: 0.5]
   [Code Root Fixup: 0.0 ms]
   [Clear CT: 0.1 ms]
   [Other: 0.6 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.5 ms]
      [Ref Enq: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 1024.0K(10.0M)->0.0B(9216.0K) Survivors: 0.0B->1024.0K Heap: 4857.6K(20.0M)->4720.2K(20.0M)]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
Total time for which application threads were stopped: 0.0036639 seconds
// 初始標記以及暫停時間0.0036639 seconds
[GC concurrent-root-region-scan-start]
[GC concurrent-root-region-scan-end, 0.0005495 secs]
// 掃描根region,耗時0.0005495 secs
[GC concurrent-mark-start]
[GC concurrent-mark-end, 0.0004523 secs]
// 併發標記,耗時0.0004523 secs
[GC remark [GC ref-proc, 0.0000279 secs], 0.0006380 secs]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
Total time for which application threads were stopped: 0.0008879 seconds
[GC cleanup 6809K->6809K(20M), 0.0006380 secs]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
 // 清除階段
Total time for which application threads were stopped: 0.0006810 seconds
[GC pause (young) (to-space exhausted), 0.0044659 secs]
   [Parallel Time: 4.0 ms, GC Workers: 4]
      [GC Worker Start (ms): Min: 176.6, Avg: 176.6, Max: 176.6, Diff: 0.0]
//Parallel Time  並行處理的部分佔用時間,Worker Start – 工做線程啓動的時刻
      [Ext Root Scanning (ms): Min: 0.3, Avg: 0.3, Max: 0.3, Diff: 0.1, Sum: 1.1]
//External root scanning  掃描外部根鎖使用的時間      
      [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
//Update Remembered Set  開始前更新緩存列表,後續併發線程能夠正確處理
         [Processed Buffers: Min: 0, Avg: 0.5, Max: 2, Diff: 2, Sum: 2]
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
//Scanning Remembered Sets 查詢指向收集區域的指針.
      [Object Copy (ms): Min: 2.6, Avg: 3.4, Max: 3.7, Diff: 1.1, Sum: 13.6]
//Object copy  每一個獨立線程複製和消亡對象鎖花費的時間
      [Termination (ms): Min: 0.0, Avg: 0.3, Max: 1.1, Diff: 1.1, Sum: 1.2]
//Termination time 當一個工做線程結束了它對特定對象的複製和掃描
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [GC Worker Total (ms): Min: 4.0, Avg: 4.0, Max: 4.0, Diff: 0.0, Sum: 15.9]
//GC Worker Total– 全部GC線程所使用的時間
      [GC Worker End (ms): Min: 180.6, Avg: 180.6, Max: 180.6, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Clear CT: 0.0 ms]
   [Other: 0.4 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.0 ms]
      [Ref Enq: 0.0 ms]
      [Free CSet: 0.0 ms]
//釋放那些已經被收集過的區域,remembered sets所花費的時間
   [Eden: 1024.0K(9216.0K)->0.0B(10.0M) Survivors: 1024.0K->0.0B Heap: 9901.8K(20.0M)->9901.8K(20.0M)]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
Total time for which application threads were stopped: 0.0046272 seconds
[Full GC 9901K->9719K(20M), 0.0030929 secs]
   [Eden: 0.0B(10.0M)->0.0B(10.0M) Survivors: 0.0B->0.0B Heap: 9901.8K(20.0M)->9719.5K(20.0M)]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC 9719K->9708K(20M), 0.0028649 secs]
   [Eden: 0.0B(10.0M)->0.0B(10.0M) Survivors: 0.0B->0.0B Heap: 9719.5K(20.0M)->9708.5K(20.0M)]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
Total time for which application threads were stopped: 0.0060723 seconds
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid11772.hprof ...
Total time for which application threads were stopped: 0.0118791 seconds
Heap dump file created [10622284 bytes in 0.012 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at com.jvm.outofmemory.HeapOutOfMemory$OutOfMemoryObject.<init>(HeapOutOfMemory.java:14)
    at com.jvm.outofmemory.HeapOutOfMemory.creatHeap(HeapOutOfMemory.java:21)
    at com.jvm.outofmemory.HeapOutOfMemory.main(HeapOutOfMemory.java:28)
Heap
 garbage-first heap   total 20480K, used 9708K [0x00000000f9a00000, 0x00000000fae00000, 0x00000000fae00000)
  region size 1024K, 1 young (1024K), 0 survivors (0K)
 compacting perm gen  total 20480K, used 2663K 
//region 大小1M,一個年輕代region,沒有幸存代region
[0x00000000fae00000, 0x00000000fc200000, 0x0000000100000000)
   the space 20480K,  13% used [0x00000000fae00000, 0x00000000fb099cd0, 0x00000000fb099e00, 0x00000000fc200000)
No shared spaces configured.

5.異常信息:

    1.java.lang.OutOfMemoryError: PermGen space:永久代被佔滿,沒法爲 class 字節碼文件分配空間,如今反射,動態代理使用愈來愈多,字節碼插樁技術能夠在類加載的先後插入自定義的內容,這些都會增大永久代的佔用。

  2.java.lang.OutOfMemoryError: Java heap space:內存泄漏 

  3.java.lang.StackOverflowError:棧空間溢出,一般是棧幀深度過深,如遞歸調用沒有出口,形成死循環。

  4.Fatal: Stack size too small:線程棧爆滿,一般是方法體代碼過多形成,能夠經過拆分方法,讓方法職責單一避免溢出,也能夠經過 -Xss 增大線程棧空間。

 

6.Java引用類型:

  1.強引用:強引用指向的對象在任什麼時候候都不會被系統回收。即便當內存不足時,VM寧願拋出內存不足的異常,也不會去回收這些對象。 

     使用場景:咱們日常大部分使用的場景都是使用了強引用,好比new建立對象,反射得到一個對象等。以下:

  Object obj = new Object();

 

  2.軟引用:一個持有軟引用的對象,不會被 JVM 很快地回收,JVM 會根據當前堆的使用狀況來判斷什麼時候回收。(一般用於緩存),若是內存空間不足時,就會回收這些對象的內存。 
軟引用還能夠和一個引用隊列進行關聯,若是這個軟引用的對象被垃圾回收,那麼VM就會將這個軟引用加入到關聯的隊列中去。 

  使用場景: 這種可用於那種有可能會在建立後使用的對象,不過爲了內存消耗會選擇使用軟引用,好比緩存。以下所示:

String cache = "{a=1,b=2,c=3,d=4}";
SoftReference<String> stringSoftReference = new SoftReference<String>(cache);
System.out.println(cache);

  3.弱引用:在系統 GC 時,只要發現弱引用,無論系統隊空間是否足夠,都會將對象進行回收。弱引用和軟引用的區別在於,只具備弱引用的對象擁有更短暫的生命週期,在垃圾回收器線程掃描它管轄的內存區域的過程當中,一旦發現對象只具備弱引用,無論當前內存空間是否足夠,都會回收他的內存。 它比軟引用的生命週期更短,和軟引用類似,它一樣能夠和引用隊列關聯,若是被垃圾回收了,就會加入到這個關聯隊列中。 

  使用場景 弱引用用於生命週期更短的,對內存更敏感的場景中,好比佔用內存很大的Map,java api中就提供了WeakHashMap使用,就會是的大Map被及時清理掉。以下:

 WeakHashMap<String,Bean> weakHashMap = new WeakHashMap<>();
 weakHashMap.put("a",new Bean(1));
 weakHashMap.put("b",new Bean(2));
 weakHashMap.put("c",new Bean(3));
 System.out.println(weakHashMap);

  4.虛引用:和沒有引用幾乎同樣。虛引用」形同虛設,與其餘幾種引用都不一樣,虛引用並不會決定對象的生命週期,若是一個對象僅持有虛引用的話,那麼它就和沒有任何的引用同樣,在任什麼時候候均可能被垃圾回收器回收。 虛引用必須和引用隊列聯合使用,引用隊列的做用和軟弱引用同樣。 

  使用場景: 我以爲他的使用場景應該在判斷一個對象是否被垃圾回收了。例子以下:

String name = "a";
ReferenceQueue<String> prq = new ReferenceQueue<>();
PhantomReference<String> nameRf = new PhantomReference<>(name, prq);
System.out.println(prq.poll());

 

接下來進行一個例子演示Java的引用類型,以下:

package com;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

/**
 * Created by cong on 2018/8/5.
 */
public class WeakReference {
    // 弱引用
    public static Map map = new WeakHashMap();
    // 強引用
    public static List list = new ArrayList();

    public static void main(String[] args) {

        for (int i = 0; i < 999; i++) {
            Integer j = new Integer(i);
            // j強引用
            list.add(j);
            // 存放在weakHashMap中的key都存在強引用,那麼weakHashMap就退化爲HashMap,即強引用
            map.put(j, new byte[i]);
            // 當內存足不足時,會報OutOfMemoryError,由於存在j強引用,沒有被使用的map存在的垃圾卻沒法被清理,形成內存泄漏
        }

//        //把for循環替換成
//        for(int i=0;i<999;i++){
//            Integer k = new  Integer(i);
//            map.put(k,new byte[i]);
//            //當內存不足時,會被自動回收,weakHashMap 會在內存緊張時,自動釋放持有弱引用的數據
//        }
    }

}

 

二.JVM 問題排查

1.CPU 使用率太高時:

  1. 經過 top 命令查看服務器的負載狀況
  2. 經過 top+H 查看線程的使用狀況
  3. 經過 jstat -gcutil pid 查看具體線程的 GC 先後內存變化(pid的地方用進程號代替)
  4. ps -mp pid -o THREAD,tid,time 查看線程列表
  5. printf "%x\n" tid 將 tid 轉換成 16 進制格式
  6. jstack pid |grep tid -A 60 打印線程(轉換後 16 進制格式的數字)tid 的堆棧信息
  7. 一般計算密集型應用產生死鎖或者死循環或超時重試致使的線程堆積都會佔用 CPU 大量資源,CPU 使用率可能達到 200%

2.線上頻繁 Full GC:

1.經過 top+H 查看線程的使用狀況

2.jmap -histo:live pid(進程號) 這個會當即觸發 Full GC

3.線上開啓了 -XX:+HeapDumpBeforeFullGC 也就是 FullGC 前保存內存快照,JVM 在執行 dump 操做的時是會發生 stop the word,此時全部的用戶線程都會暫停運行。爲了對外能正常提供服務,可用分佈式部署,匹配負載均衡

4.經過 MAT(Memory Analyzer Tool)分析內存快照,把 「Keep unreachable objects」 勾上,不然 MAT 會把堆中不可達的對象去除掉,反而不利於分析, 經過 -XX:+HeapDumpOnOutOfMemoryError 可讓虛擬機在出現內存溢出異常時 Dump 出當前的內存堆轉儲快照,而後看到 GC 日誌 Dumping heap to java_pid11772.hprof ...,咱們能夠在工程目錄裏打開 pid11772.hprof 日誌文件(在 Eclipse 應用市場下載 Memory Analyzer Tool 插件並安裝,就能夠直接打開)

5.查看 Dominator Tree 選項,內存中全部對象都按照內存消耗排名從高到低進行排序

Class Name 是 Java 類的全限定名 Shallow Heap 是對象自己消耗內存大小。

Retained Heap 是對象自己和它所引用的對象的內存大小總和。

Percenttage 是對象消耗佔整個堆快照的比率。

 

6.查看是否使用了大對象,或者長期持有對象的引用,或者大量堆積了全局的本地緩存

電商平臺,在作活動促銷時,瞬間有大量的客戶登陸,可是登陸頁面假死,形成登陸失敗,在服務器的 log 日誌發現大量的超時信息,報線程耗盡,經過 jstack pid 打印進程中線程堆棧信息發現有上百個 thread 不斷的重試發送請求,基本定位是大量超時請求重試致使服務器高負荷運行而假死。

咱們把服務端提供者登陸接口的線程併發數從 10 調成 15,並把消費者重試次數改成 0,請求失敗後直接返回,讓用戶的客戶端 Http 進行重試,同時在登陸時關閉 Druid 的 SQL 監控功能,避免增長額外資源消耗,最後從新上線,問題修復。

 

JVM 生產監控的指標有哪些?

關於 GC 的監控,我認爲最重要的三點爲:

  1. 各個區的容量。
  2. Full GC、Young GC 發生的次數。
  3. 當前系統的內存比、CPU 使用率。
咱們以 java/bin/jstat 爲例看看相關的參數細節有哪些。
數據列 描述 支持的jstat 選項
S0C Survivor0的當前容量 -gc -gccapacity -gcnew -gcnewcapacity
S1C S1的當前容量 -gc -gccapacity -gcnew -gcnewcapacity
S0U S0的使用量 -gc-gcnew
S1U S1的使用量 -gc-gcnew
EC Eden區的當前容量 -gc -gccapacity -gcnew -gcnewcapacity
EU Eden區的使用量 -gc -gcnew
OC old區的當前容量 -gc -gccapacity -gcnew -gcnewcapacity
OU old區的使用量 -gc-gcnew
PC 方法區的當前容量 -gc-gccapacity -gcold -gcoldcapacity -gcpermcapacity
PU 方法區的使用量 -gc -gcold
YGC Young GC次數 -gc -gccapacity -gcnew -gcnewcapacity -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause
YGCT Young GC累積耗時 -gc -gcnew -gcutil -gccause
FGC Full GC次數 -gc -gccapacity -gcnew -gcnewcapacity -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause
FGCT Full GC累積耗時 -gc-gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause
GCT GC總的累積耗時 -gc -gcold -gcoldcapacity -gccapacity -gcpermcapacity -gcutil -gccause
NGCMN 新生代最小容量 -gccapacity -gcnewcapacity
NGCMX 新生代最大容量 -gccapacity -gcnewcapacity
NGC 新生代當前容量 -gccapacity -gcnewcapacity
OGCMN 老年代最小容量 -gccapacity -gcoldcapacity
OGCMX 老年代最大容量 -gccapacity -gcoldcapacity
OGC 老年代當前容量 -gccapacity -gcoldcapacity
PGCMN 方法區最小容量 -gccapacity -gcpermcapacity
PGCMX 方法區最大容量 -gccapacity -gcpermcapacity
PGC 方法區當前容量 -gccapacity -gcpermcapacity
PC 方法區的當前容量 -gccapacity -gcpermcapacity
PU 方法區使用量 -gccapacity -gcold
LGCC 上一次GC發生的緣由 -gccause
GCC 當前GC發生的緣由 -gccause
TT 存活閥值,若是對象在新生代移動次數超過此閥值,則會被移到老年代 -gcnew
MTT 最大存活閥值,若是對象在新生代移動次數超過此閥值,則會被移到老年代 -gcnew
DSS survivor區的理想容量 -gcnew

輕鬆應對 JVM 的面試和實際工做。

針對面試,建議讀者保持兩點:

  • 思路清晰,說的是通的。建議讀者能夠根據做者整理的內容弄個簡單的思路導圖。
  • 面要全,有一到兩個點要細。概況起來了就三點,以下。
    • 不一樣視角的內存模型、
    • GC 對應的4種算法。
    • GC 對應的5種收集器。

針對於工做,但願你們瞭解如下幾點:

  1. 必定要知道 JDK 版本,JVM 默認參數有哪些?
  2. 我能夠設置哪些?哪些是必須設置的?
  3. 能夠直接參看不少容器,如 Tomcat、Jetty、Docker 的默認 JVM 參數。
相關文章
相關標籤/搜索