二、類加載機制與JDK調優指令
上節課程回顧:

執行引擎執行每個class檔案有兩種方式:
- JIT編譯器:即時編譯
- 位元組碼解釋器:
執行引擎可以通過這兩種方式在運作的時候執行一些class檔案。還有一些類在一開始的時候就把一些類編譯好。
java是一門既可以編譯、又可以解釋的語言。
java運作時編譯源碼(.java)成位元組碼檔案,由JRE生成。JRE由java虛拟機實作。JVM分析位元組碼後,解釋并執行。
類的生命周期
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)
負責加載使用者自定義路徑下的類包
類加載機制
全盤負責委托機制
當一個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());
}
}
發現String類的加載器沒有列印出來:因為BootStrap類加載器是使用C寫的,是以不直接展現出來,列印出來就是null。即Null表示的是BootStrap類加載器。
這裡我們自己寫一個String類:
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println("testString");
}
}
這時候根據類加載的雙親委派機制,會去java的類庫中找String,而不是直接加載我們定義的String,然後在java的String類中沒有找到main方法,是以報錯了。
這裡的父加載器并不是說加載器之間是父類關系!!!所有的加載器都實作了的是ClassLoader。
如何打破雙親委派機制: 3種。
- jdk1.2之前的動态加載;
- STR:Service Provider Interface:java提供的一種規範,具體的實作有廠商自己去實作;
mysql的這種驅動的加載方式就是打破了雙親委派機制。
DriverManager 是在rt.jar包下,而現在是“DriverManager.getConnection(url)”是調用目前類加載器去加載,此時不知道是哪個廠商的驅動(可以是mysql、sql Server等)
為什麼要打破雙親委派機制
檢視類加載資訊的JVM參數:
-verbose class
可以看出來,JVM類加載是按需加載的。
JDK性能調優監控工具
Jinfo
檢視正在運作的Java程式的擴充參數
檢視JVM的參數
使用“jinfo -flags 對應java程序運作數"即可。
檢視java系統屬性
等同于System.getProperties()
Jstat
jstat指令可以檢視堆記憶體各部分的使用量,以及加載類的數量。指令格式:
jstat [-指令選項] [vmid] [間隔時間/毫秒] [查詢次數]
Jmap
可以用來檢視記憶體資訊
堆的對象統計
jmap -histo 7824 > xxx.txt
如圖:
- Num:序号
- Instances:執行個體數量
- Bytes:占用空間大小
- Class Name:類名
這樣列印出來的是全部的對象。可以看活的對象:
jmap -histo:live 運作數 > 輸出檔案.txt
堆資訊
堆記憶體dump
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
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()));
}
}
}
可以使用jvisualvm指令工具導入檔案分析
我們可以将生成的dump檔案裝入到vm中,然後重點檢視類這一項。
這裡發現char數組執行個體很多,原因是String底層是使用final char[]來存儲的。
此時可以根據執行個體較多的類進行分析,判斷OOM問題出現的原因。
Jstack
jstack用于生成java虛拟機目前時刻的線程快照。
調優
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)