Java GC 學習實踐(上)

最近常去客戶現場,現場有問題,就把問題發給公司大佬(本身實在是菜,看不懂呀,趁進博會調休,惡補下)java

參考《深刻理解Java虛擬機》算法

目錄(Java GC 學習實踐)

  1. 淺談基礎
  • 1.1 運行時數據區(Java 1.8)
  • 1.2 垃圾收集算法
  • 1.3 垃圾收集器
  1. 解析 GC 日誌
  2. JVM 監控工具
  3. Linux 監控相關

1、淺談基礎

1. 運行時數據區

Java 1.8內存模型

1.1 程序計數器

  • (Program Counter Register)【線程隔離】
  • 可簡單理解爲當前線程所執行的字節碼行號指示器;
  • 若是是java方法,計數器記錄指向虛擬機字節碼指令地址;若是是native方法,計數器值爲undefined;
  • 【異常相關】惟一一個沒規定OOM的區域。

1.2 虛擬機棧

  • (VM Stack)【線程隔離】
  • 其描述的是Java方法執行的內存模型:【重點】每一個方法執行的過程都會建立一個棧幀(Stack Frame)用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。方法調用到執行結束,對應着一個棧幀在虛擬機棧中入棧到出棧的過程;
  • 【異常相關】若是線程請求的棧深度大於虛擬機所容許的深度,將拋出StackOverflowError異常。方法每次調用都會建立一個棧幀,而後棧幀壓棧,總不能無限壓棧吧(初看沒看懂,搜了下,發現也有人沒看懂,內心平衡點);
  • 【異常相關】大部分虛擬機棧均可以動態擴展,若是擴展時沒法申請到足夠的內存,就會OOM。
  • 代碼示例,見文末 code01.StackOverflowError

1.3 本地方法棧

  • (Native Method Stack)【線程隔離】
  • 與虛擬機棧的區別是,虛擬機棧執行java方法(字節碼)服務;本非方法棧則爲native方法服務;
  • 【異常相關】StackOverflowError、OOM。

1.4 堆

  • (Heap)【線程共享】
  • GC堆(垃圾堆),是垃圾收集器管理的主要區域;
  • java1.8以後【年輕代、老年代】。
  • 代碼示例,見文末 code02.OOM-heap

1.5 元數據區

  • (Metaspace)【線程共享】
  • jvm config example: -XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=50m
  • 元數據區取代了永久代,本質上都是方法區的實現,用來存放虛擬機加載的類信息、常量、靜態變量、JIT編譯後的代碼。
  • 代碼示例,見文末 code03.OOM-metaspace

2. 垃圾收集算法

  • 對象在否?引用計數算法、可達性分析算法
  • 標記-清除算法(先標記,再清除,清除後空間不連續,產生大量內存碎片)
  • 複製算法
    • 年輕代:Eden : Survivor = 1:8,會有10%的內存「閒置」;
    • 每次GC後,存活的對象都會放在剩餘的10%內存中,也就是To Survivor;
    • 固然,若是剩餘的10%內存不夠用呢,就須要依賴老年代進行分配擔保。
  • 標記-整理算法
    • 若是對象存活率較高,那麼複製算法就很差用了;
    • 標記-清除算法以後,將全部存活的對象都向一端移動,而後清理掉邊界之外的內存。
  • 分代收集算法
    • 典型就是分爲新生代和老年代
    • 新生代,存活率低,就使用複製算法
    • 老年代,存活率高,而且沒有額外的空間作擔保,因此使用「標記-清除」或者「標記-整理」算法

3. 垃圾收集器

3.1 新生代收集器 Serial

  • 【單線程】歷史悠久,新生代收集器,複製算法;
  • GC時要STW,直到GC完成(你媽媽在打掃衛生,你一邊亂扔紙屑,因此必須STW,你得老老實實坐着);
  • Client 模式下默認的新生代收集器(與其餘單線程收集器相比,簡單高效)。

3.2 新生代收集器 ParNew

  • 【並行多線程】新生代收集器,複製算法,Serial收集器的多線程版本;
  • 單CPU下,不會比Serial好;甚至雙CPU都不能100%超越Serial;
  • Server模式下首選新生代收集器,重要緣由是,他能和CMS(真正意義上的併發收集器)配合工做。

3.3 新生代收集器 Parallel Scavenge

  • 【Throughput】吞吐量優先
  • 【並行多線程】新生代收集器,複製算法;
  • 關注點不同,目標爲可控的吞吐量(Throughput),其餘的關注點是儘量縮短GC STW時間;
  • 吞吐量 = 運行用戶代碼時間 / (運行用戶代碼時間 + GC時間)

3.4 老年代收集器 Serial Old

  • 【單線程】老年代收集器,標記 - 整理算法;
  • 主要用戶Client 模式下虛擬機;Server模式下,1. JDK1.6之前與PS搭配使用;2. CMC收集器後背預案。

3.5 老年代收集器 Parallel Old

  • 【Throughput】吞吐量優先
  • 【並行多線程】老年代收集器,標記 - 整理算法;
  • jdk1.6之前,若是選了PS,就不能選CMS了,只能選Serial Old;
  • 吞吐量優先第一組合。

3.6 老年代收集器 CMS

  • 【併發多線程】老年代收集器,基於標記 - 清除(初始 & 併發 & 從新 標記,併發清除);
  • 關注點不同,目標爲可控的吞吐量(Throughput),其餘的關注點是儘量縮短GC STW時間;
  • 併發低停頓;缺點:CPU資源很是敏感、沒法處理浮動垃圾、基於標記清除多碎片。

3.7 G1收集器

  • 有點多,暫緩。。。

2、解析GC日誌

1. 完整GC日誌

2019-11-04T16:05:43.267+0800: 147.981: [GC (Allocation Failure) [PSYoungGen: 150496K->5938K(147456K)] 198958K->57548K(202752K), 0.0304547 secs] [Times: user=0.05 sys=0.00, real=0.03 secs]
Heap after GC invocations=39 (full 3):
PSYoungGen      total 147456K, used 5938K [0x00000000f6700000, 0x0000000100000000, 0x0000000100000000)
eden space 141312K, 0% used [0x00000000f6700000,0x00000000f6700000,0x00000000ff100000)
from space 6144K, 96% used [0x00000000ffa00000,0x00000000fffcc8f8,0x0000000100000000)
to   space 7680K, 0% used [0x00000000ff100000,0x00000000ff100000,0x00000000ff880000)
ParOldGen       total 55296K, used 51610K [0x00000000e3400000, 0x00000000e6a00000, 0x00000000f6700000)
object space 55296K, 93% used [0x00000000e3400000,0x00000000e6666870,0x00000000e6a00000)
Metaspace       used 80445K, capacity 83414K, committed 83584K, reserved 1122304K
class space    used 10018K, capacity 10577K, committed 10624K, reserved 1048576K
}
{Heap before GC invocations=40 (full 4):
PSYoungGen      total 147456K, used 5938K [0x00000000f6700000, 0x0000000100000000, 0x0000000100000000)
eden space 141312K, 0% used [0x00000000f6700000,0x00000000f6700000,0x00000000ff100000)
from space 6144K, 96% used [0x00000000ffa00000,0x00000000fffcc8f8,0x0000000100000000)
to   space 7680K, 0% used [0x00000000ff100000,0x00000000ff100000,0x00000000ff880000)
ParOldGen       total 55296K, used 51610K [0x00000000e3400000, 0x00000000e6a00000, 0x00000000f6700000)
object space 55296K, 93% used [0x00000000e3400000,0x00000000e6666870,0x00000000e6a00000)
Metaspace       used 80445K, capacity 83414K, committed 83584K, reserved 1122304K
class space    used 10018K, capacity 10577K, committed 10624K, reserved 1048576K

=====================分割線==========================
2019-11-04T16:05:43.298+0800: 148.011: [Full GC (Ergonomics) [PSYoungGen: 5938K->0K(147456K)] [ParOldGen: 51610K->48605K(83968K)] 57548K->48605K(231424K), [Metaspace: 80445K->80445K(1122304K)], 0.3256949 secs] [Times: user=0.55 sys=0.00, real=0.32 secs]
=====================。。。==========================

2. 提取主要內容

2019-11-04T16:05:43.267+0800: 147.981: [GC (Allocation Failure) [PSYoungGen: 150496K->5938K(147456K)] 198958K->57548K(202752K), 0.0304547 secs] [Times: user=0.05 sys=0.00, real=0.03 secs]
2019-11-04T16:05:43.298+0800: 148.011: [Full GC (Ergonomics) [PSYoungGen: 5938K->0K(147456K)] [ParOldGen: 51610K->48605K(83968K)] 57548K->48605K(231424K), [Metaspace: 80445K->80445K(1122304K)], 0.3256949 secs] [Times: user=0.55 sys=0.00, real=0.32 secs]

3. 分析日誌

  • 147.981148.011: JVM啓動以來通過的秒數多線程

  • GCFull GC: 表示垃圾收集停頓類型。注意:不是用來區分新生代仍是老年代的併發

    • GC (Allocation Failure) Allocation Failure 指分配失敗,也即空間不足;
    • Full GC (Ergonomics) Ergonomics 能夠理解爲自適應,表示自動的調節STW時間和吞吐量之間的平衡;
    • Full GC (System) 調用 System.gc() 觸發的GC。
  • [PSYoungGen: 150496K->5938K(147456K)]jvm

    • PSYoungGen,PS表示Parallel Scavenge收集器
    • DefNew(Default New Generation),也即便用Serial收集器
  • [ParOldGen: 51610K->48605K(83968K)]ide

    • ParOldGen,ParOld表示Parallel Old收集器,吞吐量優先
  • [XXXXXX: 150496K->5938K(147456K)]工具

    • 150496K->5938K(147456K) GC前該內存區域已使用容量 -> GC後該內存區域已使用容量(該內存區域總容量)
  • 198958K->57548K(202752K) GC前Java堆已使用容量 -> GC後Java堆已使用容量(Java堆總容量)學習

  • 0.0304547 secs GC耗時合計(secs秒)this

  • [Times: user=0.05 sys=0.00, real=0.03 secs] 用戶態CPU耗時、內核態CPU耗時和牆鍾時間spa

    • CPU時間與牆鍾時間區別:牆鍾時間包括各類非運算等待耗時,例如等待磁盤、線程阻塞;
    • 當多CPU或者多核的話,多線程會疊加這些CPU時間,因此user或sys超過real是徹底正常的。

代碼示例

code01.StackOverflowError

public class StackOverflowMain {

    public static void main(String[] args) {
        // will throw java.lang.StackOverflowError
        Test test = new Test();
        try {
            test.increment();
        } catch (StackOverflowError e) {
            System.out.println("sof error, this count is " + test.count);
            e.printStackTrace();
        }
    }

    static class Test {
        private static int count;
        void increment() {
            count++;
            increment();
        }
    }

}

code02.OOM-heap

public class OOMMain {

    private static String STR = "string";

    /**
     * -verbose:gc -XX:+HeapDumpOnOutOfMemoryError
     * -XX:HeapDumpPath=C:\\Users\\User\\Desktop\\gc
     * will throw oom by Java heap space
     */
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        while (true) {
            list.add(STR += STR);
        }
    }

}

code03.OOM-metaspace

public class OOMByCglibMain {

    /**
     * -verbose:gc -XX:+HeapDumpOnOutOfMemoryError
     * -XX:HeapDumpPath=C:\\Users\\User\\Desktop\\gc
     * -XX:MetaspaceSize=9m -XX:MaxMetaspaceSize=9m
     * will throw oom by Metaspace
     */
    public static void main(String[] args) {
        ClassLoadingMXBean loadingBean = ManagementFactory.getClassLoadingMXBean();
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMByCglibMain.class);
            enhancer.setCallbackTypes(new Class[]{Dispatcher.class, MethodInterceptor.class});
            enhancer.setCallbackFilter(new CallbackFilter() {
                @Override
                public int accept(Method method) {
                    return 1;
                }

                @Override
                public boolean equals(Object obj) {
                    return super.equals(obj);
                }
            });

            Class clazz = enhancer.createClass();
            System.out.println(clazz.getName());
            //顯示數量信息(共加載過的類型數目,當前還有效的類型數目,已經被卸載的類型數目)
            System.out.println("total: " + loadingBean.getTotalLoadedClassCount());
            System.out.println("active: " + loadingBean.getLoadedClassCount());
            System.out.println("unloaded: " + loadingBean.getUnloadedClassCount());
        }

    }

}
相關文章
相關標籤/搜索