原作:Vladimir Roubtsov
From: JavaWorld.com
原文(Javaworld.com)>>>
怎樣在Java中得到CPU的使用情況呢?這兒同時有一個好消息和一個壞消息。壞消息是不能使用純Java的方法得到CPU的使用。沒有這方面的直接的API。一個建議的替代方法是通過Runtime.exec()確定JVM的進程ID(PID),調用外部的、平台相關的命令,例如ps,然後在運行結果中解析出感興趣的PID。但是,這種方法並不理想。
好消息是,可以采用一個更為可靠的方案:跳出Java,寫幾行C代碼,然後通過JNI進行整合。下面我將向你展示編寫一個Win32平台的簡單的JNI庫是多麼簡單。
一般來說,JNI有點復雜。但是,如果你僅僅單向調用--從Java調用本地代碼,並且僅使用基本型進行通訊--事情還是很簡單的。有許多JNI方面的學習資料,所以這兒我就不介紹JNI的基礎了。我僅介紹我的實現步驟。
一、在Java中聲明JNI方法
開始,我創建一個聲明了本地方法的類com.vladium.utils.SystemInformation,該方法返回當前進程已使用的CPU的毫秒數。
public staticnative long getProcessCPUTime();
使用JDK內置的Javah工具產生將來本地代碼實現使用的C頭。
JNIEXPORT jlong JNICALL Java_com_vladium_utils_SystemInformation_getProcessCPUTime (JNIEnv * env, jclass cls)
二、本地方法實現
在大多數的Win32平台上,該方法可以使用GetProcessTimes()系統調用實現,差不多僅需要3行代碼就可以了:
JNIEXPORT jlong JNICALL Java_com_vladium_utils_SystemInformation_getProcessCPUTime (JNIEnv * env, jclass cls) { FILETIME creationTime, exitTime, kernelTime, userTime; GetProcessTimes (s_currentProcess, & creationTime, & exitTime, & kernelTime, & userTime); return (jlong) ((fileTimeToInt64 (& kernelTime) fileTimeToInt64 (& userTime)) / (s_numberOfProcessors * 10000)); }
該方法首先累加用於執行當前進程的核心和用戶代碼耗費的時間,除以處理器的數目,並把結果轉換到毫秒。fileTimeToInt64()是一個輔助函數,用於把FILETIME結構的數據轉換為64位的整數。s_currentProcess 和 s_numberOfProcessors是全局變量,當JVM裝載本地庫時即初始化。
static HANDLE s_currentProcess; static int s_numberOfProcessors; JNIEXPORT jint JNICALL JNI_OnLoad (JavaVM * vm, void * reserved) { SYSTEM_INFO systemInfo; s_currentProcess = GetCurrentProcess (); GetSystemInfo (& systemInfo); s_numberOfProcessors = systemInfo.dwNumberOfProcessors; return JNI_VERSION_1_2; }
軟件開發網
注意,如果你在UNIX平台上實現getProcessCPUTime(),你應該以getrusage系統調用開始。
三、調用本地方法
回到Java中,在SystemInformation類中,裝載本地庫(silib.dll on Win32)最好通過靜態初始化代碼塊完成。
private static final String SILIB = "silib"
; static { try { System.loadLibrary (SILIB); } catch (UnsatisfIEdLinkError e) { System.out.println ("native lib '" SILIB "' not found in 'Java.library.path': " System.getProperty ("Java.library.path")); throw e; // re-throw } }
注意,getProcessCPUTime()返回自JVM進程創建以來使用的CPU時間。就這個數據本身而言,對於這兒並沒有太多的用處。我需要更有用的Java方法來記錄不同的時刻的數據快照(data snapshots),並報告任何兩個時間點之間CPU的使用。
public static final class CPUUsageSnapshot { private CPUUsageSnapshot (long time, long CPUTime) { m_time = time; m_CPUTime = CPUTime; } public final long m_time, m_CPUTime; } // end of nested class public static CPUUsageSnapshot makeCPUUsageSnapshot () { return new CPUUsageSnapshot (System.currentTimeMillis (), getProcessCPUTime ()); } public static double getProcessCPUUsage (CPUUsageSnapshot start, CPUUsageSnapshot end) { return ((double)(end.m_CPUTime - start.m_CPUTime)) / (end.m_time - start.m_time); }
軟件開發網
四、一個簡單的CPU監視程序
“CPU監視API”基本就完成了!最後,我創建了一個singleton的線程類CPUUsageThread,它自動地每過一個時間間隔(默認是0.5秒)就拍下一個數據快照,並報告給所有的CPU使用事件的監聽者(Observer模式)。
public void run () { while (! isInterrupted ()) { final SystemInformation.CPUUsageSnapshot snapshot = SystemInformation.makeCPUUsageSnapshot (); notifyListeners (snapshot);
try { sleep (sleepTime); } catch (InterruptedException e) { return; } } }
CPUmon類是一個示例的監聽器,僅簡單地把CPU的使用情況打印輸出到System.out。
public static void main (String [] args) throws Exception { if (args.length == 0) throw new IllegalArgumentException ("usage: CPUmon <app_main_class> <app_main_args...>"); CPUUsageThread monitor = CPUUsageThread.getCPUThreadUsageThread (); CPUmon _this = new CPUmon (); Class app = Class.forName (args [0]); Method appmain = app.getMethod ("main", new Class [] {String[].class}); String [] appargs = new String [args.length - 1]; System.arraycopy (args, 1, appargs, 0, appargs.length); monitor.addUsageEventListener (_this); monitor.start (); appmain.invoke (null, new Object [] {appargs}); }
http://www.mscto.com
另外,為了能夠在啟動要監視的應用程序之前開始CPUUsageThread,CPUmon.main()包裝了另一個Java主類。
作為演示,我運行CPUmon和JDK1.3.1的SwingSet2示例程序(不要忘了把silib.dll安裝到OS的PATH環境變量或者Java.library.path系統屬性所覆蓋的路徑下):
>java -DJava.library.path=. -cp silib.jar;(my JDK install dir)\demo\jfc\SwingSet2\SwingSet2.jar CPUmon SwingSet2
[PID: 339] CPU usage: 46.8%
[PID: 339] CPU usage: 51.4%
[PID: 339] CPU usage: 54.8%
(while loading, the demo uses nearly 100% of one of the two CPUs on my Machine)
...
[PID: 339] CPU usage: 46.8%
[PID: 339] CPU usage: 0%
[PID: 339] CPU usage: 0%
(the demo finished loading all of its panels and is mostly idle)
...
[PID: 339] CPU usage: 100%
[PID: 339] CPU usage: 98.4%
[PID: 339] CPU usage: 97%
(I switched to the ColorChooserDemo panel which ran a CPU-intensive
animation that used both of my CPUs)
...
[PID: 339] CPU usage: 81.4%
[PID: 339] CPU usage: 50%
[PID: 339] CPU usage: 50%
(I used Windows NT Task Manager to adjust the CPU affinity for the
"Java" process to use a single CPU)
...
當然,我也可以通過任務管理器查看到CPU使用信息,這兒的要點是現在我們可以以編程方式記錄該信息。對於長時間運行測試和服務器應用診斷程序,遲早會派上用場。本文附帶的完整的庫中添加了其它一些有用的本地方法,其中一個可以得到進程的PID(用於與外部工具整合)。
資源
1. Download the complete library that accompanIEs this article:
http://www.javaworld.com/javaworld/Javaqa/2002-11/cpu/01-qa-1108-cpu.zip
2. JNI specification and tutorials:
http://Java.sun.com/J2SE/1.4/docs/guide/jni/index.Html
3. In "Java Tip 92: Use the JVM Profiler Interface for Accurate Timing," Jesper Gortz explores an alternative direction for profiling CPU usage. (However, using JVMPI requires more work to compute CPU usage for the entire process compared to this article's solution): http://www.mscto.com
http://www.javaworld.com/javaworld/javatips/jw-Javatip92.Html