天天看點

課堂筆記:JVM(二)類加載機制與JDK調優指令二、類加載機制與JDK調優指令

二、類加載機制與JDK調優指令

上節課程回顧:

課堂筆記:JVM(二)類加載機制與JDK調優指令二、類加載機制與JDK調優指令

執行引擎執行每個class檔案有兩種方式:

  • JIT編譯器:即時編譯
  • 位元組碼解釋器:

執行引擎可以通過這兩種方式在運作的時候執行一些class檔案。還有一些類在一開始的時候就把一些類編譯好。

java是一門既可以編譯、又可以解釋的語言。

java運作時編譯源碼(.java)成位元組碼檔案,由JRE生成。JRE由java虛拟機實作。JVM分析位元組碼後,解釋并執行。

課堂筆記:JVM(二)類加載機制與JDK調優指令二、類加載機制與JDK調優指令

類的生命周期

課堂筆記:JVM(二)類加載機制與JDK調優指令二、類加載機制與JDK調優指令

1.加載

将.class檔案從磁盤讀到記憶體

通過類的全限定名(寫出類的完整路徑)。

2.連接配接

2.1 驗證

驗證位元組碼檔案的正确性

2.2 準備

給類的靜态變量配置設定記憶體,并賦予預設值

2.3 解析

類裝載器裝入類所引用的其它所有類

3.初始化

為類的靜态變量賦予正确的初始值,上述的準備階段為靜态變量賦予的是虛拟機預設的初始值,此處賦予的才是程式編寫者為變量配置設定的真正的初始值,執行靜态代碼塊

4.使用

5.解除安裝

類加載器的種類

啟動類加載器(Bootstrap ClassLoader)

負責加載JRE的核心類庫,如JRE目标下的rt.jar,charsets.jar等

擴充類加載器(Extension ClassLoader)

負責加載JRE擴充目錄ext中jar類包

系統類加載器(Application ClassLoader)

負責加載ClassPath路徑下的類包

使用者自定義加載器(User ClassLoader)

負責加載使用者自定義路徑下的類包

課堂筆記:JVM(二)類加載機制與JDK調優指令二、類加載機制與JDK調優指令

類加載機制

全盤負責委托機制

當一個ClassLoader加載一個類的時候,除非顯示的使用另一個ClassLoader,該類所依賴和引用的類也由這個ClassLoader載入

雙親委派機制

指先委托父類加載器尋找目标類,在找不到的情況下載下傳自己的路徑中查找并載入目标類

雙親委派模式的優勢
  • 沙箱安全機制:比如自己寫的String.class類不會被加載,這樣可以防止核心庫被随意篡改
  • 避免類的重複加載:當父ClassLoader已經加載了該類的時候,就不需要子ClassLoader再加載一次

這裡我們自己看看類加載:

import com.sun.crypto.provider.DESKeyFactory;
public class ClassLoaderDemo {
    public static void main(String[] args) {
        System.out.println(String.class.getClassLoader());
        System.out.println(DESKeyFactory.class.getClassLoader().getClass().getName());
        System.out.println(ClassLoaderDemo.class.getClassLoader());
        System.out.println(ClassLoader.getSystemClassLoader().getClass().getName());

    }
}
           
課堂筆記:JVM(二)類加載機制與JDK調優指令二、類加載機制與JDK調優指令

發現String類的加載器沒有列印出來:因為BootStrap類加載器是使用C寫的,是以不直接展現出來,列印出來就是null。即Null表示的是BootStrap類加載器。

這裡我們自己寫一個String類:

package java.lang;
public class String {
    public static void main(String[] args) {
        System.out.println("testString");
    }
}
           
課堂筆記:JVM(二)類加載機制與JDK調優指令二、類加載機制與JDK調優指令

這時候根據類加載的雙親委派機制,會去java的類庫中找String,而不是直接加載我們定義的String,然後在java的String類中沒有找到main方法,是以報錯了。

這裡的父加載器并不是說加載器之間是父類關系!!!所有的加載器都實作了的是ClassLoader。

如何打破雙親委派機制: 3種。

  • jdk1.2之前的動态加載;
  • STR:Service Provider Interface:java提供的一種規範,具體的實作有廠商自己去實作;
課堂筆記:JVM(二)類加載機制與JDK調優指令二、類加載機制與JDK調優指令

mysql的這種驅動的加載方式就是打破了雙親委派機制。

DriverManager 是在rt.jar包下,而現在是“DriverManager.getConnection(url)”是調用目前類加載器去加載,此時不知道是哪個廠商的驅動(可以是mysql、sql Server等)

為什麼要打破雙親委派機制

檢視類加載資訊的JVM參數:

-verbose class
           
課堂筆記:JVM(二)類加載機制與JDK調優指令二、類加載機制與JDK調優指令

可以看出來,JVM類加載是按需加載的。

JDK性能調優監控工具

Jinfo

檢視正在運作的Java程式的擴充參數

檢視JVM的參數

課堂筆記:JVM(二)類加載機制與JDK調優指令二、類加載機制與JDK調優指令

使用“jinfo -flags 對應java程序運作數"即可。

檢視java系統屬性

等同于System.getProperties()

課堂筆記:JVM(二)類加載機制與JDK調優指令二、類加載機制與JDK調優指令

Jstat

jstat指令可以檢視堆記憶體各部分的使用量,以及加載類的數量。指令格式:

jstat [-指令選項] [vmid] [間隔時間/毫秒] [查詢次數]

Jmap

可以用來檢視記憶體資訊

堆的對象統計

jmap -histo 7824 > xxx.txt

如圖:

課堂筆記:JVM(二)類加載機制與JDK調優指令二、類加載機制與JDK調優指令
  • Num:序号
  • Instances:執行個體數量
  • Bytes:占用空間大小
  • Class Name:類名

這樣列印出來的是全部的對象。可以看活的對象:

jmap -histo:live 運作數 > 輸出檔案.txt
           

堆資訊

課堂筆記:JVM(二)類加載機制與JDK調優指令二、類加載機制與JDK調優指令

堆記憶體dump

課堂筆記:JVM(二)類加載機制與JDK調優指令二、類加載機制與JDK調優指令

jmap -dump:format=b,file=temp.hprof

也可以在設定記憶體溢出的時候自動導出dump檔案(項目記憶體很大的時候,可能會導不出來)

1.-XX:+HeapDumpOnOutOfMemoryError
2.-XX:HeapDumpPath=輸出路徑
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:\oomdump.dump
           
課堂筆記:JVM(二)類加載機制與JDK調優指令二、類加載機制與JDK調優指令
package com.luban;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class OutOfMemortTest {
    /**
     *  設定JVM參數
     *  -Xms10m
     *  -Xmx10m
     *  -XX:+PrintGCDetails
     *  -XX:+HeapDumpOnOutOfMemoryError
     *  -XX:HeapDumpPath=./  (路徑)
     */
    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        long i = 0;
        while (true) {
            list.add(new User(i ++, UUID.randomUUID().toString()));
        }
    }
}
           
課堂筆記:JVM(二)類加載機制與JDK調優指令二、類加載機制與JDK調優指令

可以使用jvisualvm指令工具導入檔案分析

課堂筆記:JVM(二)類加載機制與JDK調優指令二、類加載機制與JDK調優指令

我們可以将生成的dump檔案裝入到vm中,然後重點檢視類這一項。

課堂筆記:JVM(二)類加載機制與JDK調優指令二、類加載機制與JDK調優指令

這裡發現char數組執行個體很多,原因是String底層是使用final char[]來存儲的。

此時可以根據執行個體較多的類進行分析,判斷OOM問題出現的原因。

Jstack

jstack用于生成java虛拟機目前時刻的線程快照。

課堂筆記:JVM(二)類加載機制與JDK調優指令二、類加載機制與JDK調優指令

調優

JVM調優主要就是調整下面兩個名額

停頓時間:垃圾收集器做垃圾回收中斷應用執行的時間。-XX:MaxGCPauseMillis

吞吐量:垃圾收集的時間和總時間的占比:1/(1+n),吞吐量為1-1/(1+n)。-XX:GCTimeRatio=n

死鎖程式:

package com.luban;

public class DeadLock {
    private static Object lock1 = new Object();
    private static Object lock2 = new Object();
    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lock1) {
                System.out.println("Thread1 start");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (lock2) {
                    System.out.println("Thread1 end");
                }
            }

        }).start();

        new Thread(() -> {
            synchronized (lock2) {
                try {
                    System.out.println("Thread2 start");
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (lock1) {
                    System.out.println("Thread2 end");
                }
            }

        }).start();

        System.out.println("The End");
    }
}
           

此時會形成死鎖,程序不會結束。

随後我們生成該程式的線程快照:

jstack 39204 > deadlock.txt
           
2020-05-29 08:32:18
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.201-b09 mixed mode):

"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x00000000027b4800 nid=0x6ad0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Thread-1" #12 prio=5 os_prio=0 tid=0x000000001b8d2000 nid=0x94f4 waiting for monitor entry [0x000000001c2af000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at com.luban.DeadLock.lambda$main$1(DeadLock.java:33)
	- waiting to lock <0x0000000780a4e260> (a java.lang.Object)
	- locked <0x0000000780a4e270> (a java.lang.Object)
	at com.luban.DeadLock$$Lambda$2/664223387.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)

"Thread-0" #11 prio=5 os_prio=0 tid=0x000000001b8cf800 nid=0x94cc waiting for monitor entry [0x000000001c1af000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at com.luban.DeadLock.lambda$main$0(DeadLock.java:17)
	- waiting to lock <0x0000000780a4e270> (a java.lang.Object)
	- locked <0x0000000780a4e260> (a java.lang.Object)
	at com.luban.DeadLock$$Lambda$1/885284298.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)

"Service Thread" #10 daemon prio=9 os_prio=0 tid=0x000000001ae8c800 nid=0x9e20 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x000000001ae43800 nid=0x777c waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x000000001adc2000 nid=0x94ec waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x000000001adc1000 nid=0x9640 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000000001adbf800 nid=0x8d84 runnable [0x000000001b3ae000]
   java.lang.Thread.State: RUNNABLE
	at java.net.SocketInputStream.socketRead0(Native Method)
	at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
	at java.net.SocketInputStream.read(SocketInputStream.java:171)
	at java.net.SocketInputStream.read(SocketInputStream.java:141)
	at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
	at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
	at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
	- locked <0x0000000780b21b20> (a java.io.InputStreamReader)
	at java.io.InputStreamReader.read(InputStreamReader.java:184)
	at java.io.BufferedReader.fill(BufferedReader.java:161)
	at java.io.BufferedReader.readLine(BufferedReader.java:324)
	- locked <0x0000000780b21b20> (a java.io.InputStreamReader)
	at java.io.BufferedReader.readLine(BufferedReader.java:389)
	at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001acf3800 nid=0x5f70 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000000019962800 nid=0x2ea8 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x00000000028ad000 nid=0x85e4 in Object.wait() [0x000000001acaf000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x0000000780808ed0> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
	- locked <0x0000000780808ed0> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
	at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x00000000028a4000 nid=0x82a4 in Object.wait() [0x000000001abae000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x0000000780806bf8> (a java.lang.ref.Reference$Lock)
	at java.lang.Object.wait(Object.java:502)
	at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
	- locked <0x0000000780806bf8> (a java.lang.ref.Reference$Lock)
	at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"VM Thread" os_prio=2 tid=0x0000000019917800 nid=0x9bb0 runnable 

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00000000027ca000 nid=0x8588 runnable 

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00000000027cb800 nid=0x6bd4 runnable 

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00000000027cd000 nid=0x92ac runnable 

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00000000027ce800 nid=0x968c runnable 

"VM Periodic Task Thread" os_prio=2 tid=0x000000001ae98800 nid=0x8300 waiting on condition 

JNI global references: 317


Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x00000000028a9e08 (object 0x0000000780a4e260, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00000000028aa018 (object 0x0000000780a4e270, a java.lang.Object),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
	at com.luban.DeadLock.lambda$main$1(DeadLock.java:33)
	- waiting to lock <0x0000000780a4e260> (a java.lang.Object)
	- locked <0x0000000780a4e270> (a java.lang.Object)
	at com.luban.DeadLock$$Lambda$2/664223387.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)
"Thread-0":
	at com.luban.DeadLock.lambda$main$0(DeadLock.java:17)
	- waiting to lock <0x0000000780a4e270> (a java.lang.Object)
	- locked <0x0000000780a4e260> (a java.lang.Object)
	at com.luban.DeadLock$$Lambda$1/885284298.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.
           

随後我們可以在最後或者前面分析發現死鎖情況的發生。

GC調優步驟

1.列印GC日志

-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:./gc.log

Tomcat可以直接加載JAVA_OPTS變量裡

2.分析日志得到關鍵性名額

3.分析GC原因,調優JVM參數

1.Parallel Scavenge收集器(預設)

分析parallel-gc.log

第一次調優,設定Metaspace大小:增大元空間大小-XX:MetaspaceSize=64M -XX:MaxMetaspaceSize=64M

第二次調優,增大年輕代動态擴容增量(預設是20%),可以減少YGC:-XX:YoungGenerationSizeIncrement=30

比較下幾次調優效果:

吞吐量 最大停頓 平均停頓 YGC FGC
97.467% 370 ms 50.0 ms 16 2
98.9% 110 ms 32.5 ms 12

2.配置CMS收集器

-XX:+UseConcMarkSweepGC

分析gc-cms.log

3.配置G1收集器

-XX:+UseG1GC

分析gc-g1.log

young GC:[GC pause (G1 Evacuation Pause)(young)

initial-mark:[GC pause (Metadata GC Threshold)(young)(initial-mark) (參數:InitiatingHeapOccupancyPercent)

mixed GC:[GC pause (G1 Evacuation Pause)(Mixed) (參數:G1HeapWastePercent)

full GC:[Full GC (Allocation Failure)(無可用region)

(G1内部,前面提到的混合GC是非常重要的釋放記憶體機制,它避免了G1出現Region沒有可用的情況,否則就會觸發 FullGC事件。CMS、Parallel、Serial GC都需要通過Full GC去壓縮老年代并在這個過程中掃描整個老年代。G1的Full GC算法和Serial GC收集器完全一緻。當一個Full GC發生時,整個Java堆執行一個完整的壓縮,這樣確定了最大的空餘記憶體可用。G1的Full GC是一個單線程,它可能引起一個長時間的停頓時間,G1的設計目标是減少Full GC,滿足應用性能目标。)

檢視發生MixedGC的門檻值:jinfo -flag InitiatingHeapOccupancyPercent 程序ID

調優:

第一次調優,設定Metaspace大小:增大元空間大小-XX:MetaspaceSize=64M -XX:MaxMetaspaceSize=64M

第二次調優,添加吞吐量和停頓時間參數:-XX:GCTimeRatio=99 -XX:MaxGCPauseMillis=10

GC常用參數

堆棧設定

-Xss:每個線程的棧大小

-Xms:初始堆大小,預設實體記憶體的1/64

-Xmx:最大堆大小,預設實體記憶體的1/4

-Xmn:新生代大小

-XX:NewSize:設定新生代初始大小

-XX:NewRatio:預設2表示新生代占年老代的1/2,占整個堆記憶體的1/3。

-XX:SurvivorRatio:預設8表示一個survivor區占用1/8的Eden記憶體,即1/10的新生代記憶體。

-XX:MetaspaceSize:設定元空間大小

-XX:MaxMetaspaceSize:設定元空間最大允許大小,預設不受限制,JVM Metaspace會進行動态擴充。

垃圾回收統計資訊

-XX:+PrintGC

-XX:+PrintGCDetails

-XX:+PrintGCTimeStamps

-Xloggc:filename

收集器設定

-XX:+UseSerialGC:設定串行收集器

-XX:+UseParallelGC:設定并行收集器

-XX:+UseParallelOldGC:老年代使用并行回收收集器

-XX:+UseParNewGC:在新生代使用并行收集器

-XX:+UseParalledlOldGC:設定并行老年代收集器

-XX:+UseConcMarkSweepGC:設定CMS并發收集器

-XX:+UseG1GC:設定G1收集器

-XX:ParallelGCThreads:設定用于垃圾回收的線程數

并行收集器設定

-XX:ParallelGCThreads:設定并行收集器收集時使用的CPU數。并行收集線程數。

-XX:MaxGCPauseMillis:設定并行收集最大暫停時間

-XX:GCTimeRatio:設定垃圾回收時間占程式運作時間的百分比。公式為1/(1+n)

CMS收集器設定

-XX:+UseConcMarkSweepGC:設定CMS并發收集器

-XX:+CMSIncrementalMode:設定為增量模式。适用于單CPU情況。

-XX:ParallelGCThreads:設定并發收集器新生代收集方式為并行收集時,使用的CPU數。并行收集線程數。

-XX:CMSFullGCsBeforeCompaction:設定進行多少次CMS垃圾回收後,進行一次記憶體壓縮

-XX:+CMSClassUnloadingEnabled:允許對類中繼資料進行回收

-XX:UseCMSInitiatingOccupancyOnly:表示隻在到達閥值的時候,才進行CMS回收

-XX:+CMSIncrementalMode:設定為增量模式。适用于單CPU情況

-XX:ParallelCMSThreads:設定CMS的線程數量

-XX:CMSInitiatingOccupancyFraction:設定CMS收集器在老年代空間被使用多少後觸發

-XX:+UseCMSCompactAtFullCollection:設定CMS收集器在完成垃圾收集後是否要進行一次記憶體碎片的整理

G1收集器設定

-XX:+UseG1GC:使用G1收集器

-XX:ParallelGCThreads:指定GC工作的線程數量

-XX:G1HeapRegionSize:指定分區大小(1MB~32MB,且必須是2的幂),預設将整堆劃分為2048個分區

-XX:GCTimeRatio:吞吐量大小,0-100的整數(預設9),值為n則系統将花費不超過1/(1+n)的時間用于垃圾收集

-XX:MaxGCPauseMillis:目标暫停時間(預設200ms)

-XX:G1NewSizePercent:新生代記憶體初始空間(預設整堆5%)

-XX:G1MaxNewSizePercent:新生代記憶體最大空間

-XX:TargetSurvivorRatio:Survivor填充容量(預設50%)

-XX:MaxTenuringThreshold:最大任期門檻值(預設15)

-XX:InitiatingHeapOccupancyPercen:老年代占用空間超過整堆比IHOP門檻值(預設45%),超過則執行混合收集

-XX:G1HeapWastePercent:堆廢物百分比(預設5%)

-XX:G1MixedGCCountTarget:參數混合周期的最大總次數(預設8)

lection:設定CMS收集器在完成垃圾收集後是否要進行一次記憶體碎片的整理

G1收集器設定

-XX:+UseG1GC:使用G1收集器

-XX:ParallelGCThreads:指定GC工作的線程數量

-XX:G1HeapRegionSize:指定分區大小(1MB~32MB,且必須是2的幂),預設将整堆劃分為2048個分區

-XX:GCTimeRatio:吞吐量大小,0-100的整數(預設9),值為n則系統将花費不超過1/(1+n)的時間用于垃圾收集

-XX:MaxGCPauseMillis:目标暫停時間(預設200ms)

-XX:G1NewSizePercent:新生代記憶體初始空間(預設整堆5%)

-XX:G1MaxNewSizePercent:新生代記憶體最大空間

-XX:TargetSurvivorRatio:Survivor填充容量(預設50%)

-XX:MaxTenuringThreshold:最大任期門檻值(預設15)

-XX:InitiatingHeapOccupancyPercen:老年代占用空間超過整堆比IHOP門檻值(預設45%),超過則執行混合收集

-XX:G1HeapWastePercent:堆廢物百分比(預設5%)

-XX:G1MixedGCCountTarget:參數混合周期的最大總次數(預設8)