【JVM實戰】基於JDK命令行工具的監控

基於JDK命令行工具的監控

一、JVM的參數類型

JVM的參數類型主要分成三類

  • 標準參數
  • X參數
  • XX參數

標準參數,在JVM的各個版本中基本不變的(儘可能保持兼容),是相對比較穩定的參數。

比方說,大家在第一次安裝Java後,都會敲的命令行

java  -version

在這裏插入圖片描述
裏面會顯示JVM的一些基本信息,比如版本號,和編譯方式,這裏的minxed mode表示混合編譯,還有Server VM 表示JVM的運行模式是Server模式。

除了-version還有-help

java -help

它可以列舉一些Java的標準參數,並標有解釋。
在這裏插入圖片描述
下面還有很多,這裏僅僅列舉一部分。

這裏說兩個參數: -server 和 -client

JVM有兩種運行模式Server與Client。兩種模式的區別在於,Client模式啓動速度較快,Server模式啓動較慢;但是啓動進入穩定期長期運行之後Server模式的程序運行速度比Client要快很多。這是因爲Server模式啓動的JVM採用的是重量級的虛擬機,對程序採用了更多的優化;而Client模式啓動的JVM採用的是輕量級的虛擬機。

server:默認爲堆提供了一個更大的空間和並行的垃圾收集器 並且在運行時可以更大程度的優化代碼

client:客戶端虛擬機有較小的默認堆內存 可以縮短JVM啓動的時間和佔用更少的內存 客戶端的JVM只有在32位操作系統中才有

值得我們關注的是:

  1. 從JDK5開始 當應用啓動時會檢測當前的運行環境是否是服務器 如果是服務器就使用Server JVM
  2. 在JDK6中 Server JVM要求至少雙核CPU和2GB物理內存
  3. 在32位操作系統上 JDK可以運行Server JVM 但是JRE只能運行Client JVM

這些瞭解即可,一般不會變動。

X參數

X參數和XX參數都是非標準化參數,在各個JVM版本中可能會變化。

不過X參數相對於XX參數,使用頻率低很多,下面看幾個常見的。

設置JVM的編譯方式:

  • -Xint:解釋執行
  • -Xcomp:第一次使用就編譯成本地代碼
  • -Xmixed:混合模式,JVM自己來決定是否如何編譯本地代碼

使用演示:
在這裏插入圖片描述
在這裏插入圖片描述
這裏再還原成混合模式:
在這裏插入圖片描述

默認的混合模式 是最好的。

XX參數

XX參數主要分爲兩大類:

Boolean類型

格式:

-XX:[+-] <name> 表示啓用或者禁用了name參數 +表示啓用

比如:

  • -XX:+UseConcMarkSweepGC表示啓用了CMS垃圾回收器
  • -XX:+UseG1GC 表示啓用了G1垃圾回收器

非Boolean(Key-Value類型)

格式:

-XX:<name>=<value> 表示name參數的值是value

比如:

  • -XX:MaxGCPauseMills=500表示GC最大的停頓時間是500
  • ​ -XX:GCTimeRatio=19

GCTimeRatio參數的值應當是一個大於0且小於100的整數,也就是垃圾收集時間佔總時間的比率,相當於是吞吐量的倒數。如果把此參數設置爲19,那允許的最大GC時間就佔總時間的5%(即1/(1+19)),默認值爲99,就是允許最大1%(即1/(1+99))的垃圾收集時間。

-XX參數常見的參數還有一種縮寫模式(雖然是-X開頭,但是其實是-XX參數)

比如:

  • -Xms 等價於-XX:InitialHeapSize 表示初始化的堆大小
  • -Xmx 等價於-XX:MaxHeapSize 表示最大的堆的大小
  • -Xss 等價於 -XX:ThreadStackSize 線程堆棧的大小

二、JDK的命令行監控工具

查看JVM運行時參數的值

1、-XX:+PrintFlagsFina

1 ) -XX:+PrintFlagsFinal 打印出所有XX參數和值

java -XX: +PrintFlagsFinal

在這裏插入圖片描述
=表示默認值
:= 表示被用戶或者JVM修改後的值

2、-XX:+PrintFlagsInitial

java -XX:+PrintFlagsInitial

打印的是XX參數的初始化值

這裏列舉一小部分:

在這裏插入圖片描述
3、-XX:+PrintCommandLineFlags

這個參數讓JVM打印出那些已經被用戶或者JVM設置過的詳細的XX參數的名稱和值。

它列舉出 -XX:+PrintFlagsFinal的結果中第三列有”:=”的參數。

以這種方式,我們可以用-XX:+PrintCommandLineFlags作爲快捷方式來查看修改過的參數

正常使用

java -XX:+PrintCommandLineFlags

可以查看到垃圾回收器的信息

別人測試的都能看到垃圾回收器的信息,我的JVM並不能看到這個信息,暫時沒有找到原因,這裏記錄一下。下面兩張圖引自傳送門

在這裏插入圖片描述
在這裏插入圖片描述

4、-XX:+UnlockDiagnosticVMOptions解鎖診斷參數

5、-XX:+UnlockExperimentalVMOptions解鎖實驗參數

這兩條後面會補上。

(1)jps

在介紹這幾款工具之前,有必要說明:JDK內置命令行工具的監控的詳細信息都可以在這裏找到 傳送門

jps是虛擬機進程狀況工具

下面到了JDK的命令行監控工具的使用,首先是jps,使用非常簡單。

Java的jps 是專門查看Java進程的
在這裏插入圖片描述
加一個 -l參數,可以看到完整的名稱
在這裏插入圖片描述

jps工具主要選項
在這裏插入圖片描述

(2)jinfo

jinfo 是 Java配置信息工具

jinfo(Configuration Info for Java)的作用是實時查看和調整虛擬機各項參數。

使用jps命令的-v參數可以查看虛擬機啓動時顯式指定的參數列表,但如果想知道未被顯式指定的參數的系統默認值,除了去找資料外,就只能使用jinfo的-flag選項進行查詢了(如果只限於JDK 6或以上版本的話,使用java-XX:+PrintFlagsFinal查看參數默認值也是一個很好的選擇)。

jinfo還可以使用-sysprops選項把虛擬機進程的System.getProperties()的內容打印出來。這個命令在JDK5時期已經隨着Linux版的JDK發佈,當時只提供了信息查詢的功能,JDK 6之後,jinfo在Windows和Linux平臺都有提供,並且加入了在運行期修改部分參數值的能力(可以使用-flag[+|-]name或者-flag name=value在運行期修改一部分運行期可寫的虛擬機參數值)。在JDK 6中,jinfo對於Windows平臺功能仍然有較大限制,只提供了最基本的-flag選項。

可以使用 jinfo -flag <參數名稱> 命令行工具去查看當前java進程的JVM參數。

比如:

jinfo -flag MaxHeapSize pid

在這裏插入圖片描述
查看線程堆棧的大小 這裏顯示是1024KB。
在這裏插入圖片描述
查看是否使用了啓用相關的配置。比如壓縮指針。
在這裏插入圖片描述
+ 表示有
- 表示沒有

在堆中,32位的對象引用(指針)佔4個字節,而64位的對象引用佔8個字節。也就是說,64位的對象引用大小是32位的2倍。64位JVM在支持更大堆的同時,由於對象引用變大卻帶來了性能問題。爲了能夠保持32位的性能,oop須保留32位。方法是壓縮指針

(3)jstat

jstat是虛擬機統計信息監視工具

確切點來說,jstat(JVM Statistics Monitoring Tool)是用於監視虛擬機各種運行狀態信息的命令行工具。它可以顯示本地或者遠程[插圖]虛擬機進程中的類加載、內存、垃圾收集、即時編譯等運行時數據,在沒有GUI圖形界面、只提供了純文本控制檯環境的服務器上,它將是運行期定位虛擬機性能問題的常用工具。

比如類裝載信息、垃圾收集信息、以及JIT編譯信息等

命令格式如下:

在這裏插入圖片描述
option主要有如下內容:

-class 查看類加載信息
-compiler 查看編譯信息
-gc 查看垃圾回收信息

更多的選項,可以從-help查看,或者想要了解更多, JDK內置命令行工具的監控的詳細信息都可以在這裏找到 傳送門

演示

java -class 進程號 間隔毫秒數 輸出次數

在這裏插入圖片描述

-class option
Class loader statistics. 

Loaded: Number of classes loaded. -- 加載的類的個數
Bytes: Number of kBs loaded.  -- 加載的類的大小
Unloaded: Number of classes unloaded -- 卸載的類的個數.
Bytes: Number of Kbytes unloaded.  -- 卸載的類的大小
Time: Time spent performing class loading and unloading operations. -- 花在加載和卸載類的時間

垃圾收集的內容還是比較多的。

java -gc 進程號  間隔毫秒數 輸出次數

在這裏插入圖片描述

參數比較多哈,但是根據下面的註釋 很容易區分出JVM中的每一個分塊。

其中S0C、S0U。C表示總容量大小,U表示使用大小,這樣就好理解了。

  • S0C、S1C、S0U、S1U:S0和S1的總量與使用量
  • EC、EU:Eden區總量與使用量
  • OC、OU:Old區總量與使用量
  • MC、MU: Metaspace區總量與使用量
  • CCSC、CCSU:壓縮類空間總量與使用量(壓縮指針的作用)
  • YGC、YGCT:Young Go的次數與時間
  • FGC、FGCT:FullGC的次數與時間
  • GCT:總的GC時間
-gc option
Garbage-collected heap statistics.

S0C: Current survivor space 0 capacity (kB).  -- 
S1C: Current survivor space 1 capacity (kB).
S0U: Survivor space 0 utilization (kB). 
S1U: Survivor space 1 utilization (kB).
EC: Current eden space capacity (kB).
EU: Eden space utilization (kB).
OC: Current old space capacity (kB).
OU: Old space utilization (kB).
MC: Metaspace capacity (kB).
MU: Metacspace utilization (kB).
CCSC: Compressed class space capacity (kB).
CCSU: Compressed class space used (kB).
YGC: Number of young generation garbage collection events.
YGCT: Young generation garbage collection time.
FGC: Number of full GC events.
FGCT: Full garbage collection time.
GCT: Total garbage collection time.

除此之外 還有別的option。具體需要查文檔,演示的內容是比較常用的。

下面有必要說一下Java的內存結構:

JVM主要分成兩大塊,一塊是堆區、一塊是非堆區。

堆區又分成兩大塊,一塊是Young、一塊是Old。

如果young區是複製算法的話(現在的商用Java虛擬機大多都優先採用了這種收集算法去回收新生代)。

Young區會被分成兩大塊,一塊是S0+S1、另外一塊是Eden,對象優先在Eden分配。

發生垃圾收集時,將Eden和Survivor中仍然存活的對象一次性複製到另外一塊Survivor空間上,然後直接清理掉Eden和已用過的那塊Survivor空間。

HotSpot虛擬機默認Eden和Survivor的大小比例是8∶1,也即每次新生代中可用內存空間爲整個新生代容量的90%(Eden的80%加上一個Survivor的10%),只有一個Survivor空間,即10%的新生代是會被“浪費”的。
在這裏插入圖片描述
非堆區採用的是操作系統的本地內存,Java8中引入了元空間(方法區移入Metaspace),裏面有兩個重要的內容,一個是如果啓用了壓縮指針,就會存在CCS。CodeCahe中存放的是JIT的代碼信息(Java代碼轉換成的native代碼、JNI代碼)。

下面看一下JIT編譯的情況

java  -compiler 進程號
java -printcompilation 進程號

在這裏插入圖片描述

-compiler option
Java HotSpot VM Just-in-Time compiler statistics.

Compiled: Number of compilation tasks performed. -- 完成多少編譯任務(將方法編譯成本地代碼)
Failed: Number of compilations tasks failed.  -- 失敗的
Invalid: Number of compilation tasks that were invalidated. -- 錯誤的、無效的
Time: Time spent performing compilation tasks. -- 時間
FailedType: Compile type of the last failed compilation. 
FailedMethod: Class name and method of the last failed compilation.

在這裏插入圖片描述

Compiled: Number of compilation tasks performed by the most recently compiled method.
Size: Number of bytes of byte code of the most recently compiled method.
Type: Compilation type of the most recently compiled method.
Method: Class name and method name identifying the most recently compiled method. Class name uses slash (/) instead of dot (.) as a name space separator. Method name is the method within the specified class. The format for these two fields is consistent with the HotSpot -XX:+PrintCompilation option.

其它選項的說明:
在這裏插入圖片描述

(4)jmap

jmap是Java內存映像工具

jmap(Memory Map for Java)命令可以用於生成堆轉儲快照(一般稱爲heapdump或dump文件),還可以查詢finalize執行隊列、Java堆和方法區的詳細信息,如空間使用率、當前用的是哪種收集器等。

和jinfo命令一樣,jmap有部分功能在Windows平臺下是受限的,除了生成堆轉儲快照的-dump選項和用於查看每個類的實例、空間佔用統計的-histo選項在所有操作系統中都可以使用之外,其餘選項都只能在Linux/Solaris中使用。

命令格式

jmap [option] vmid  進程號

option選項的合法值與具體含義如表示。
在這裏插入圖片描述

通過jmap可以定位內存溢出的問題。

我們看一下堆溢出

public class Main {

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

結果:java.lang.OutOfMemoryError

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.base/java.util.Arrays.copyOf(Arrays.java:3721)
	at java.base/java.util.Arrays.copyOf(Arrays.java:3690)
	at java.base/java.util.ArrayList.grow(ArrayList.java:235)
	at java.base/java.util.ArrayList.grow(ArrayList.java:242)
	at java.base/java.util.ArrayList.add(ArrayList.java:452)
	at java.base/java.util.ArrayList.add(ArrayList.java:465)
	at com.leetcodePractise.test.Main.main(Main.java:13)

Process finished with exit code 1

再看一下非堆溢出,非堆指的是非堆內存,主要包括元空間 , Jvm Stack(java虛擬機棧), Local Method Statck(本地方法棧)。

public class Main {

    public static void main(String[] args) {
        new Main().test();
    }

    public void test() {
        test();
    }
}

結果:java.lang.StackOverflowError

Exception in thread "main" java.lang.StackOverflowError
	at com.leetcodePractise.test.Main.test(Main.java:10)
	at com.leetcodePractise.test.Main.test(Main.java:10)
	at com.leetcodePractise.test.Main.test(Main.java:10)
	at com.leetcodePractise.test.Main.test(Main.java:10)
	....

當發生內存溢出的時候,我們希望有內存映像文件的自動導出,來幫助我們去進行分析。這裏有兩種方式,一種是通過參數的設置

-XX:+HeapDumpOnOutOfMemoryError   意思是當發生內存溢出的時候將Heap dump出來
-XX:HeapDumpPath=./  意思是導出的文件存放在哪個路徑下面

另外一種方式是使用jmap命令手動導出
在這裏插入圖片描述
dump文件直接打開是亂碼的,如果我們要分析dump文件,需要藉助工具MAT,可以參考這篇文章傳送門

下面演示一下其他的option

-heap 顯示每個區塊佔多大的內存。
在這裏插入圖片描述
其他的選項使用類型。

(5)jhat

jhat是虛擬機堆轉儲快照分析工具

JDK提供jhat(JVM Heap Analysis Tool)命令與jmap搭配使用,來分析jmap生成的堆轉儲快照。

jhat內置了一個微型的HTTP/Web服務器,生成堆轉儲快照的分析結果後,可以在瀏覽器中查看。

不過,在實際工作中,除非手上真的沒有別的工具可用,否則多數人是不會直接使用jhat命令來分析堆轉儲快照文件的,主要原因有兩個方面。

  1. 一般不會在部署應用程序的服務器上直接分析堆轉儲快照,即使可以這樣做,也會盡量將堆轉儲快照文件複製到其他機器上進行分析,因爲分析工作是一個耗時而且極爲耗費硬件資源的過程,既然都要在其他機器上進行,就沒有必要再受命令行工具的限制了。
  2. jhat的分析功能相對來說比較簡陋,有大量工具都能實現比jhat更強大專業的分析功能。比如上面介紹的MAT。

(6)jstack

jstack是Java堆棧跟蹤工具

jstack(Stack Trace for Java)命令用於生成虛擬機當前時刻的線程快照(一般稱爲threaddump或者javacore文件)。線程快照就是當前虛擬機內每一條線程正在執行的方法堆棧的集合,生成線程快照的目的通常是定位線程出現長時間停頓的原因,如線程間死鎖、死循環、請求外部資源導致的長時間掛起等,都是導致線程長時間停頓的常見原因。線程出現停頓時通過jstack來查看各個線程的調用堆棧,就可以獲知沒有響應的線程到底在後臺做些什麼事情,或者等待着什麼資源。

jstack命令格式:

jstack [option] 進程號

選項
在這裏插入圖片描述
演示
在這裏插入圖片描述
這是JIT負責編譯的線程
在這裏插入圖片描述
線程狀態是一項非常重要的信息,記牢了。
在這裏插入圖片描述

想必大家都曾遇到這樣的問題:爲什麼有時中CPU佔用會過高,怎麼查看與解決?

關於CPU佔用過高的原因大致有這幾條:
1、大型程序:可能是編寫的程序有不合理的地方,也有可能是電腦配置低
2、病毒與木馬:病毒、木馬造成。比如大量的蠕蟲病毒在系統內部迅速複製,造成CPU佔用資源率據高不下。
3、磁盤碎片:經常對文檔進行復制和刪除,會使得硬盤中數據排列非常分散,使計算機在查找的時候速度變慢,從而佔用大量的CPU。

關於排查可以這樣做:

通過top命令查看是哪個進程導致CPU佔用率高 然後shitf+p 倒序排列

在這裏插入圖片描述
如果是Java進程引起的問題,我們就需要看一下Java進程中的詳細信息了。

在終端執行 top -H -p ,查詢pid進程下的所有線程消耗cpu的情況,然後shift+p倒序找到消耗cpu最高的線程id,如圖:

在這裏插入圖片描述

日誌文件中線程id是採用16進制的,所以要將查詢出來的10進制線程id轉換成16進制,數字轉換的方式有很多中,在這裏通過linux命令printf轉換,如下:

在這裏插入圖片描述

使用jstack下載堆棧信息日誌文件,sudo -u appadmin jstack 179258 >/usr/local/test2.txt,如下:

因爲當前用戶是root,而非179258進程所屬用戶appadmin,因此需要切換用戶。然後根據進程id將對應的日誌重定向到某個目錄下,當然也可以在線查找 sudo -u appadmin jstack <進程id> | grep -a <16進制線程id>.

生成的日誌文件位於linux上,可以通過sz下載到windows下進行查找,根據上文轉成16進制的線程id進行全局搜索,就能定位到具體的原因,如圖:

在這裏插入圖片描述
根據以上步驟能快速找到問題所在,但是這個日誌文件的分析是需要經驗積累的,只有當出現諸如線程死鎖的情況下,會出現很明顯的日誌,deadLock關鍵字樣,並且能提示出現問題的代碼所在行。其他問題如網絡帶寬,文件io等硬件資源跟不上的原因導致,是需要有一定的經驗積累才能發現的!

參考《深入理解Java虛擬機:JM高級特性與最佳實踐》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章