本文介紹了JVM記憶體、GC、線程、類、檔案控制代碼等指標的採集原理。
記憶體相關指標
ARMS探針中存在一個定時任務,通過定期調用JDK的介面 java.lang.management.ManagementFactory#getPlatformMXBeans(java.lang.Class<T>)並傳入 java.lang.management.MemoryPoolMXBean.class參數獲得JVM記憶體相關指標。
該方法會返回一個列表,列表中每一個對象反映了JVM中不同記憶體預期使用方式。下面的程式碼片段簡單地示範了如何使用該方法。
public static void printCurrentJVMMemoryUsage() {
List<MemoryPoolMXBean> beans = ManagementFactory.getPlatformMXBeans(MemoryPoolMXBean.class);
for (MemoryPoolMXBean bean : beans) {
System.out.printf("area=%s\tname=%s\tinitial=%d\tmax=%d\tcommited=%d\tuse=%d\n", bean.getType().name(),
bean.getName(), bean.getUsage().getInit(), bean.getUsage().getMax(), bean.getUsage().getCommitted(), bean.getUsage().getUsed());
}
}該程式碼片段在不同JVM版本中的輸出有所不同,一個可能的輸出結果如下所示,包含了每一個記憶體地區的類型,名稱以及初始大小、最大大小、已提交量和已使用量。
area=NON_HEAP name=Code Cache initial=2555904 max=134217728 commited=3211264 use=3139712
area=NON_HEAP name=Metaspace initial=0 max=-1 commited=8388608 use=7950584
area=NON_HEAP name=Compressed Class Space initial=0 max=1073741824 commited=1048576 use=919408
area=HEAP name=PS Eden Space initial=67108864 max=1409286144 commited=67108864 use=39057856
area=HEAP name=PS Survivor Space initial=11010048 max=11010048 commited=11010048 use=0
area=HEAP name=PS Old Gen initial=179306496 max=2863661056 commited=179306496 use=0ARMS探針定期採集這些資料,採集到後不做任何額外加工直接上報並記錄在下述指標中。
指標名 | 指標含義 |
arms_jvm_mem_init_bytes | 記憶體初始大小 |
arms_jvm_mem_max_bytes | 記憶體最大大小 |
arms_jvm_mem_committed_bytes | 記憶體已提交大小 |
arms_jvm_mem_used_bytes | 記憶體已使用大小 |
其中兩個關鍵維度:
維度名稱 | 維度含義 | 維度舉例 |
area | 記憶體地區類型 | heap、nonheap |
id | 記憶體地區名稱 | eden、survivor、total |
JVM各個記憶體地區說明請參見JVM監控記憶體詳情說明。
GC相關指標
針對4.4.0以下版本探針,ARMS探針中存在一個定時任務,通過定期調用JDK的介面 java.lang.management.ManagementFactory#getPlatformMXBeans(java.lang.Class<T>)並傳入 java.lang.management.GarbageCollectorMXBean參數獲得JVM GC相關指標;針對4.4.0及以上版本探針,通過訂閱由 GarbageCollectorMXBean提供的 GarbageCollectionNotificationInfo事件擷取相關資料。
該方法會返回一個列表,列表中每一個對象反映了JVM中不同垃圾收集器的運行情況。下面的程式碼片段簡單地示範了如何使用該方法。
public static void printGC() {
List<GarbageCollectorMXBean> beans = ManagementFactory.getPlatformMXBeans(GarbageCollectorMXBean.class);
for (GarbageCollectorMXBean bean : beans) {
System.out.printf("name=%s\tgcCount=%d\tgcTime=%d\n", bean.getName(),
bean.getCollectionCount(), bean.getCollectionTime());
}
}該程式碼片段在不同JVM版本中的輸出有所不同,一個可能的輸出結果如下所示,包含了每一個記憶體回收行程的名稱、GC次數以及GC耗時。
name=PS Scavenge gcCount=0 gcTime=0
name=PS MarkSweep gcCount=0 gcTime=0需要注意的是,因為該資料記錄的是JVM啟動以來的累計值,ARMS探針在定期採集到這些資料後會減去上一次採集的值得到當前周期(預設15秒)內的GC次數和GC耗時,然後上報並記錄在下述指標中。
除了ZGC和Shenandoah類型的GC外,其他類型的GC耗時都是指Stop the world(STW,即GC過程中暫停所有Java業務線程的執行)耗時。在ZGC和Shenandoah中,Pauses對應STW耗時,Cycles表示一次GC的總耗時。
指標名 | 指標含義 |
arms_jvm_gc_delta | 當前周期GC次數 |
arms_jvm_gc_seconds_delta | 當前周期GC耗時 |
考慮到實際的記憶體回收行程數量較多,為降低使用者理解成本, ARMS探針會將實際的記憶體回收行程名稱映射為Young和Old並記錄在下述維度中。
維度名稱 | 維度含義 | 維度舉例 |
gen | GC類型 | young、old |
cause | GC觸發原因(4.4.0及以上版本探針支援) | System.gc()、Heap Dump Initiated GC、Allocation Failure等。 |
具體的映射關係如下:
記憶體回收行程名稱 | Gen取值 |
Copy | young |
G1 Young Gen | young |
G1 Old Gen | old |
G1 Young Generation | young |
G1 Old Generation | old |
ParNew | young |
ConMarkSweep | old |
ConcurrentMarkSweep | old |
PS Scavenge | young |
PS MarkSweep | old |
PS Serial | young |
MarkSweepCompact | old |
由於在最新的ZGC中,整體GC的設計有較大變動,不再適用之前簡單的YoungGC、FullGC這種分類方式,ZGC單獨映射如下:
記憶體回收行程名稱 | Gen取值 |
ZGC Cycles | cycles |
ZGC Pauses | pauses |
ZGC Minor Cycles | young_cycles |
ZGC Minor Pauses | young_pauses |
ZGC Major Cycles | old_cycles |
ZGC Major Pauses | old_pauses |
需要注意,在ARMS控制台上會展示一個叫做線程棧使用的圖表,如下圖所示,該指標並非在ARMS探針側採集,是通過當前存活線程數 × 1MB計算所得。JVM中預設會為每個線程分配1MB的空間用於儲存棧幀中的局部變數表、運算元棧、動態連結和方法返回地址等資訊。

線程相關指標
ARMS探針中存在一個定時任務,通過定期調用JDK的介面 java.lang.management.ManagementFactory#getThreadMXBean獲得JVM線程相關指標。
該方法會返回一個類型為java.lang.management.ThreadMXBean的執行個體,該執行個體反映了當前JVM的線程情況。下面的程式碼片段簡單地示範了如何使用該方法。
public static void printThreads() {
ThreadMXBean threadMXBean = java.lang.management.ManagementFactory.getThreadMXBean();
int threadCount = threadMXBean.getThreadCount();
int daemonThreadCount = threadMXBean.getDaemonThreadCount();
long totalStartedThreadCount = threadMXBean.getTotalStartedThreadCount();
long terminatedThreadCount = totalStartedThreadCount - threadCount;
System.out.printf("allStartedThreadCount=%d\tcurrentThreadCount=%d\tdaemonThreadCount=%d\tterminatedThreadCount=%d\n"
, totalStartedThreadCount, threadCount, daemonThreadCount, terminatedThreadCount);
}該程式碼片段在不同JVM版本中的輸出有所不同,一個可能的輸出結果如下所示,包含了當前JVM累計啟動的線程數、存活的線程數、後台線程數以及累計的終結線程數。需要注意,除了終結線程數之外,其餘幾個線程數均是通過直接調用ThreadMXBean方法獲得,累計的終結線程數是通過累計啟動線程數減去當前存活線程數獲得,代表了從JVM啟動後,累計有多少線程完成執行任務而被終結回收。
allStartedThreadCount=5 currentThreadCount=5 daemonThreadCount=4 terminatedThreadCount=0此外對於當前存活的線程數,會分別統計其處於RUNNABLE、BLOCKED、WAITING、TIMED_WAITING的線程數量。
ARMS探針會定期採集這些資料,採集後不做額外處理直接記錄在下述指標中。
指標名 | 指標含義 |
arms_jvm_threads_count | 記憶體初始大小 |
上文提到的不同線程通過下述關鍵維度區分。
維度名稱 | 維度含義 | 維度舉例 |
state | 線程狀態 |
對於live線程,會分別統計下述不同狀態的線程數:
|
類相關指標
ARMS探針中存在一個定時任務,通過定期調用JDK的介面 ManagementFactory.getClassLoadingMXBean()獲得JVM類載入相關指標。
該方法會返回一個類型為java.lang.management.ClassLoadingMXBean的執行個體,該執行個體反映了當前JVM的線程情況。下面的程式碼片段簡單地示範了如何使用該方法。
public static void printCurrentJVMMemoryUsage() {
ClassLoadingMXBean classLoaderMXBean = ManagementFactory.getClassLoadingMXBean();
long totalLoadedClassCount = classLoaderMXBean.getTotalLoadedClassCount();
long unloadedClassCount = classLoaderMXBean.getUnloadedClassCount();
System.out.printf("totalLoadedClassCount=%d\tunloadedClassCount=%d\n"
, totalLoadedClassCount, unloadedClassCount);
}該程式碼片段在不同JVM版本中的輸出有所不同,一個可能的輸出結果如下所示,包含了當前JVM累計載入的類數量和累計卸載的類數量。
totalLoadedClassCount=1355 unloadedClassCount=0需要注意,因為該資料記錄的是JVM啟動以來的累計值,ARMS探針在定期採集到這些資料後會減去上一次採集的值得到當前周期(預設15秒)內的類載入數量和類卸載數量,然後上報並記錄在下述指標中。
指標名 | 指標含義 |
arms_class_load_loaded | 當前周期載入類數量 |
arms_class_load_un_loaded | 當前周期卸載類數量 |
檔案控制代碼相關指標
ARMS探針中存在一個定時任務,通過定期調用JDK的介面 ManagementFactory.getOperatingSystemMXBean()獲得JVM類載入相關指標。
該方法會返回一個類型為java.lang.management.OperatingSystemMXBean的執行個體,該執行個體反映了當前JVM的檔案控制代碼開啟情況。下面的程式碼片段簡單地示範了如何使用該方法。
public static void printFDUsage() {
OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean();
if (!(operatingSystemMXBean instanceof UnixOperatingSystemMXBean)) {
return;
}
long openFDCount = ((UnixOperatingSystemMXBean) operatingSystemMXBean).getOpenFileDescriptorCount();
long maxFDCount = ((UnixOperatingSystemMXBean) operatingSystemMXBean).getMaxFileDescriptorCount();
System.out.printf("openFDCount=%d\tfdOpenRatio=%f\n"
, openFDCount, (double) openFDCount / maxFDCount);
}該程式碼片段在不同的JVM版本輸出有所不同,一個可能的輸出結果如下所示,結果中輸出了當前JVM開啟的檔案控制代碼數和檔案控制代碼開啟率,檔案控制代碼開啟率是由當前JVM已經開啟的檔案控制代碼數除以當前JVM總的可開啟檔案控制代碼數得到。
openFDCount=184 fdOpenRatio=0.017969ARMS 探針會定期採集這些資料,採集後不做額外處理直接記錄在下述指標中。
指標名 | 指標含義 |
arms_file_desc_open_count | 當前JVM開啟的檔案控制代碼數 |
arms_file_desc_open_ratio | 當前周期卸載類數量 |