需求:在同一台 Windows x86 機器上,由第一個 JVM 啟動第二個 JVM 執行測試程式,並由第一個 JVM 的 Java 程式監控第二個 JVM 的記憶體與 CPU 使用情況,以比較不同程式在相同 JVM 環境下的資源消耗差異。
Runtime.getRuntime().exec() 啟動第二個 JVM 執行目標程式。tasklist 命令將進程資訊導出至文字檔。// 1. 啟動第二個 JVM 執行測試程式
Runtime rt = Runtime.getRuntime();
String[] cmdStart = { "C:\\jdk1.8\\jre\\bin\\java", "-cp", "test.jar", "com.test.Main" };
Process proc = rt.exec(cmdStart);
// 2. 定期執行 tasklist 導出進程快照
String[] cmdTasklist = { "cmd", "/c", "tasklist /fo csv /nh > process_log.csv" };
rt.exec(cmdTasklist);
// 3. 解析 CSV 檔案,提取目標進程(PID 或映像名稱)的 Mem Usage、CPU Time
// 可使用 BufferedReader 逐行讀取,String.split(",") 分欄位
| 優點 | 缺點 |
|---|---|
| 純 Java,無需原生庫,跨平台潛力大(換命令即可) | 輪詢間隔導致數據非即時,精度受限於 tasklist 更新頻率 |
| 實現極簡,幾行代碼即可完成核心功能 | 需處理 CSV 解析、PID 追蹤、子進程管理等邊界情況 |
| 不侵入被測程式,無副作用 | 大量進程時 I/O 開銷明顯 |
native 方法,載入共享庫(.dll/.so)。OpenProcess, GetProcessMemoryInfo, GetProcessTimes)精準讀取進程記憶體、CPU 時間。System.Diagnostics.Process 類簡化開發。// Java 宣告
public class ProcessMonitor {
static { System.loadLibrary("procmon"); }
public native long getMemoryUsage(int pid);
public native double getCpuUsage(int pid);
}
// C++ 實現 (Windows)
#include
#include
#include
JNIEXPORT jlong JNICALL Java_ProcessMonitor_getMemoryUsage(JNIEnv *env, jobject obj, jint pid) {
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
if (!hProcess) return -1;
PROCESS_MEMORY_COUNTERS pmc;
if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc))) {
CloseHandle(hProcess);
return pmc.WorkingSetSize; // 實體記憶體使用量
}
CloseHandle(hProcess);
return -1;
}
編寫 C# 類庫(ProcessMonitorLib.dll)封裝 System.Diagnostics.Process,再透過 JNI4NET 或自訂 COM/管道 互操作,Java 呼叫 C# 方法獲取數據。但需注意:被測 JVM 若自身使用 JNI,可能觸發衝突導致數據不準;若僅做相對比較且被測程式無 JNI 調用,此方案可行。
| 優點 | 缺點 |
|---|---|
| 毫秒級精度,可獲取私有/虛擬記憶體、核心/使用者 CPU 時間等細粒度指標 | 需編寫/編譯原生代碼,部署需攜帶平台相關動態庫 |
| 無輪詢延遲,事件驅動可行 | JNI 開發門檻高,易發生崩潰導致 JVM 退出 |
| 可整合更多系統級指標(句柄數、執行緒數、I/O 計數器) | 跨平台移植需重寫原生層 |
Java 監控外部 JVM 進程,Runtime.exec + tasklist 是輕量級首選;JNI 提供極致精度但複雜度高。實務中多建議直接使用標準化觀測體系(JMX, Micrometer, OpenTelemetry)植入應用內部,避免外部採樣的固有盲區與干擾。