前面東西說完後,現在可以說一些和我們平時進行效能調優相關的東西了,那怎麼看和我們JVM效能調優相關的東西呢,其實這對我們開發來說是一個比較頭痛的問題,其實我們JDK官網給了一些我們相關的指令,我們可以用這些命令去排查當前JAVA中當前有多少個程式、可以知道我們記憶體空間中他是一個什麼樣的結構、哪些物件佔用的比較大、哪些物件佔用的比較小、還有檢視我們的類載入資訊、檢視GC資訊等。
官網:https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/index.html
一、jps
我們不管查什麼資訊,首先第一個針對的物件肯定是java的程式而言的,所以第一個要說的命令就是檢視當前系統的java的程式。在演示之前先隨便開啟一個自己的java專案,這個只要是開發人員都會有,我就不上傳DEMO了。啟動專案後然後開啟本地的命令列。
輸入命令jps後可以看到我啟動的專案java程式和程式號PID
接下來所有的操作都可以以這個程式號進行開展
二、jinfo
(1)實時檢視和調整JVM配置引數
The jinfo command prints Java configuration information for a specified Java process or core file or a remote debug server.
The configuration information includes Java system properties and Java Virtual Machine (JVM) command-line flags.
(2)檢視用法
jinfo -flflag name PID 檢視某個java程式的name屬性的值
jinfo -flag MaxHeapSize PID
jinfo -flag UseG1GC PID #檢視是否用到了G1,有'-'號表示沒有用到
(3)修改
引數只有被標記為manageable的flflags可以被實時修改
jinfo -flag [+|-] PID
jinfo -flag <name>=<value> PID
(4)檢視曾經賦過值的一些引數
jinfo -flags PID #檢視全部
三、jstat
檢視程式的類的資訊和GC的資訊
(1)檢視虛擬機器效能統計資訊
The jstat command displays performance statistics for an instrumented Java HotSpot VM.
The target JVM is identified by its virtual machine identifier, or vmid option.
(2)檢視類裝載資訊
jstat -class PID 1000 10 檢視某個java程式的類裝載資訊,每1000毫秒輸出一次,共輸出10次
可以通過上面可以看到有多少個類被裝載進來了,也可以看到有多少個類被解除安裝了
(3)檢視垃圾收集資訊
jstat -gc PID 1000 10
可以看到s0/s1什麼一堆資訊
四、jstack
前面我們看到了程式、類和gc的資訊了,接下來要看的就是要看執行緒的資訊了。
(1)檢視執行緒堆疊資訊
The jstack command prints Java stack traces of Java threads for a specified Java process, core file, or remote debug server.
(2)用法
jstack PID
這裡面有很多執行緒,有業務執行緒和GC執行緒,查到這些有什麼用呢,其實這個其實用處很多,比喻排查死鎖情況
(4)排查死鎖案例
public class DeadLockDemo { public static void main(String[] args) { DeadLock d1=new DeadLock(true); DeadLock d2=new DeadLock(false); Thread t1=new Thread(d1); Thread t2=new Thread(d2); t1.start(); t2.start(); } } //定義鎖物件 class MyLock{ public static Object obj1=new Object(); public static Object obj2=new Object(); } //死鎖程式碼 class DeadLock implements Runnable{ private boolean flag; DeadLock(boolean flag){ this.flag=flag; } public void run() { if(flag) { while(true) { synchronized(MyLock.obj1) { System.out.println(Thread.currentThread().getName()+"----if 獲得obj1鎖"); synchronized(MyLock.obj2) { System.out.println(Thread.currentThread().getName()+"--- -if獲得obj2鎖"); } } } }else { while(true){ synchronized(MyLock.obj2) { System.out.println(Thread.currentThread().getName()+"----否則 獲得obj2鎖"); synchronized(MyLock.obj1) { System.out.println(Thread.currentThread().getName()+"--- -否則獲得obj1鎖"); } } } } } }
jsack分析
把列印資訊拉到最後可以發現
會發現兩個執行緒發生了死鎖
五、jmap
上面這些內容看完後其實還有一個很重要的東西沒看,那就是執行時資料區堆的執行情況
(1)生成堆轉儲快照
The jmap command prints shared object memory maps or heap memory details of a specified process, core file, or remote debug server.
(2)列印出堆記憶體相關資訊
jmap -heap PID
(3)dump出堆記憶體相關資訊
jmap -dump:format=b,file=heap.hprof PID
(4)要是在發生堆記憶體溢位的時候,能自動dump出該檔案就好了
一般在開發中,JVM引數可以加上下面兩句,這樣記憶體溢位時,會自動dump出該檔案
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap.hprof
(5)關於dump下來的檔案可以結合工具來分析,這塊後面再說。
六、執行引擎
Person.java原始碼檔案是Java這門高階開發語言,對程式設計師友好,方便我們開發。javac編譯器將Person.java原始碼檔案編譯成class檔案[我們把這裡的編譯稱為前期編譯],交給JVM執行,因為JVM只能認識class位元組碼檔案。同時在不同的作業系統上安裝對應版本的JDK,裡面包含了各自遮蔽作業系統底層細節的JVM,這樣同一份class檔案就能執行在不同的作業系統平臺之上,得益於JVM。這也是Write Once,Run Anywhere的原因所在。
最終JVM需要把位元組碼指令轉換為機器碼,可以理解為是0101這樣的機器語言,這樣才能執行在不同的機器上,那麼由位元組碼轉變為機器碼是誰來做的呢?說白了就是誰來執行這些位元組碼指令的呢?這就是執行引擎。
6.1、解釋執行
Interpreter,直譯器逐條把位元組碼翻譯成機器碼並執行,跨平臺的保證。剛開始執行引擎只採用瞭解釋執行的,但是後來發現某些方法或者程式碼塊被呼叫執行的特別頻繁時,就會把這些程式碼認定為“熱點程式碼”。
6.2 即時編譯器
Just-In-Time compilation(JIT),即時編譯器先將位元組碼編譯成對應平臺的可執行檔案,執行速度快。即時編譯器會把這些熱點程式碼編譯成與本地平臺關聯的機器碼,並且進行各層次的優化,儲存到記憶體中。
6.3 JVM採用哪種方式
JVM採取的是混合模式,也就是解釋+編譯的方式,對於大部分不常用的程式碼,不需要浪費時間將其編譯成機器碼,只需要用到的時候再以解釋的方式執行;對於小部分的熱點程式碼,可以採取編譯的方式,追求更高的執行效率。
6.4 即使編譯器型別
(1)HotSpot虛擬機器裡面內建了兩個JIT:C1和C2
- C1也稱為Client Compiler,適用於執行時間短或者對啟動效能有要求的程式
- C2也稱為Server Compiler,適用於執行時間長或者對峰值效能有要求的程式
(2)Java7開始,HotSpot會使用分層編譯的方式
也就是會結合C1的啟動效能優勢和C2的峰值效能優勢,熱點方法會先被C1編譯,然後熱點方法中的熱點會被C2再次編譯
6.5 AOT和Graal VM
6.5.1 AOT
在Java9中,引入了AOT(Ahead-Of-Time)編譯器,即時編譯器是在程式執行過程中,將位元組碼翻譯成機器碼。而AOT是在程式執行之前,將位元組碼轉換為機器碼
優勢 :這樣不需要在執行過程中消耗計算機資源來進行即時編譯
劣勢 :AOT 編譯無法得知程式執行時的資訊,因此也無法進行基於類層次分析的完全虛方法內聯,或者基於程式 profifile 的投機性優化(並非硬性限制,我們可以通過限制執行範圍,或者利用上一次執行的程式 profifile 來繞開這兩個限制)
6.5.2 Graal VM
在Java10中,新的JIT編譯器Graal被引入它是一個以Java為主要程式語言,面向位元組碼的編譯器。跟C++實現的C1和C2相比,模組化更加明顯,也更加容易維護。Graal既可以作為動態編譯器,在執行時編譯熱點方法;也可以作為靜態編譯器,實現AOT編譯。除此之外,它還移除了程式語言之間的邊界,並且支援通過即時編譯技術,將混雜了不同的程式語言的,程式碼編譯到同一段二進位制碼之中,從而實現不同語言之間的無縫切換。