怎样在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 JNICALLJava_com_vladium_utils_SystemInformation_getProcessCPUTime (JNIEnv * env, jclass cls)

二、本地方法实现
在大多数的Win32平台上,该方法可以使用GetProcessTimes()系统调用实现,差不多仅需要3行代码就可以了:

JNIEXPORT jlong JNICALLJava_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 JNICALLJNI_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}); }

另外,为了能够在启动要监视的应用程序之前开始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.zip2. JNI specification and tutorials:
http://java.sun.com/j2se/1.4/docs/guide/jni/index.html3. 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.javaworld.com/javaworld/javatips/jw-javatip92.html

why edited on 2005-09-06 00:14