Software Development

Measure CPU and memory consumption of a Java application

The Java library described in this document will measure native Java process size and current CPU usage using standard APIs for both a Windows XP and Solaris operating system environment.

Measuring CPU and memory consumption often becomes a crucial point in achieving proper performance of Java application. And while Java provides some methods for measuring its heap size, there is no way to measure native Java process size and current CPU usage using standard APIs. Results of such measurements can be very important for developers, and will provide information about application performance and effectiveness in the real time. Unfortunately, such information can be mined only from the operating system directly, which is beyond the scope of standard Java portability.

The main trick consists of using native OS-dependent system calls, and transferring data to Java through JNI (Java Native Interface). This is a reliable solution, opposite to calling an external platform-specific command like ps and parsing its output. When I was confronted with such a problem, I tried to use an original tiny library written by Vladimir Roubtsov, which was able to measure CPU time of a process under Win32 system only. However, the abilities of that library were quite limited, and moreover I needed some way to measure CPU and memory under both Windows and Solaris platforms.

I have extended the library's abilities and implemented all functionality for Windows and Solaris 8 platforms. The new library can measure pure CPU time, percentage CPU consumption, native free and used memories, native memory size of a Java process, system information like OS name, patches, hardware info etc. It is implemented in three parts: common Java part, Windows implementation, and Solaris implementation. OS-dependent parts are implemented in plain C.


Editor's Note: The downloadable version of this article includes all of the code listings in a more manageable text format.


Library


So, we'll create a simple JNI library for talking to an operating system in a C layer and providing the resulting data to a Java application. First, we create a SystemInformation class (Listing A) providing a simple API for measuring and recording CPU usage and other system-dependent information. The class is abstract because it exposes a completely static API.

(Listing A)


The most important method is getProcessCPUTime(), which returns a number of milliseconds of CPU time (kernel + user) used by the current process, or by the process whose PID has been transferred to the native library while initializing. The returned value should be adjusted for the number of processors in the system; below we'll see how this is done in native code. We declare the method with modifier native, meaning that it has to be implemented in the JNI code. The method getProcessCPUTime() is used to make a CPU usage data snapshots by associating measured CPU time with current system time. Such data snapshots are being made in a makeCPUUsageSnapshot() method, outputting a CPUUsageSnapshot container object. Then the idea of CPU percentage measuring becomes easy: we make two CPU usage snapshots with a known time interval between them, and compute CPU usage as a fraction of 1.0 between two time points by division of difference in process CPU times by difference in system times. Here is how the getProcessCPUUsage() method works:

public static double getProcessCPUUsage (final CPUUsageSnapshot start, final CPUUsageSnapshot end)
    {
        if (start == null) throw new IllegalArgumentException ("null input: start");
        if (end == null) throw new IllegalArgumentException ("null input: end");
        if (end.m_time < start.m_time + MIN_ELAPSED_TIME)
            throw new IllegalArgumentException ("end time must be at least " + MIN_ELAPSED_TIME + " ms later than start time");
       
        return ((double)(end.m_CPUTime - start.m_CPUTime)) / (end.m_time - start.m_time);
    }

As long as we have the time interval between snapshots in the denominator, and the CPU time that the process has spent in an active state in the numerator, we’ll have a percentage of CPU usage of the process during the measured interval; 1.0 corresponds to 100 percent utilization of all processors.

In fact this approach is used in all versions of the UNIX ps utility and in Windows Task Manager Application for monitoring percentage CPU usage of a particular process. It is obvious that the longer time interval, the more average and inaccurate result we will get. However, a minimum time difference should be enforced for the inputs into getProcessCPUUsage(). The motivation for this restriction is the fact that System.currentTimeMillis() on some system has a low resolution. Solaris 8 operating system provides a system call for direct obtaining percentage CPU usage from the kernel tables. For this purpose we have a getProcessCPUPercentage() method which will return CPU time used by the process in percents. If the feature is not supported by the OS (under Windows, for example), JNI library should return a negative number by design of our application.

There are also some other native declarations that have to be implemented in native part:

  • getCPUs() returns the number of processors on machine
  • getMaxMem() returns the maximum physical memory available in the system
  • getFreeMem() returns current free memory in the system
  • getSysInfo() returns some system info including some hardware and operating system details
  • getMemoryUsage() returns current space allocated for the process, in Kbytes (those pages may or may not be in memory)
  • getMemoryResident() returns current process space being resident in memory, in Kbytes.

All of those methods often can be very helpful for different Java developers. In order to make sure that the native JNI library is loaded into memory and initialized before any native method is called, the library is loaded in a static initializer:

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
        }
    }

After initializing a .dll (or .so) library we can use native-declared methods in a straightforward way:

final SystemInformation.CPUUsageSnapshot m_prevSnapshot =
SystemInformation.makeCPUUsageSnapshot ();
Thread.sleep(1000);
final SystemInformation.CPUUsageSnapshot event =
SystemInformation.makeCPUUsageSnapshot ();
final long memorySize = SystemInformation.getMemoryUsage();
final long residentSize = SystemInformation.getMemoryResident();
long freemem = SystemInformation.getFreeMem()/1024;
long maxmem = SystemInformation.getMaxMem()/1024;
double receivedCPUUsage = 100.0 * SystemInformation.getProcessCPUUsage (m_prevSnapshot, event);
System.out.println("Current CPU usage is "+receivedCPUUsage+"%”);

Now let’s examine JNI native implementations for Windows and Solaris, respectively. C header file silib.h (Listing-B) can be generated by the Javah tool from the JDK, or written by hand.

(Listing B)


Windows


First we’ll consider Windows implementation (Listing C).

(Listing C)


There exist two special functions in JNI, JNI_OnLoad and JNI_OnUnload, which are called when the library is loaded and unloaded, respectively. JNI_OnLoad is executed once before any other methods are called and can be conveniently used for initializing variables that will not change throughout the lifetime of this process, and for negotiating JNI spec versions as well.

By default the library will measure the parameters of its own process, but it can be overloaded from the Java application by calling systemInformation.setPid() method. The s_PID C variable will hold the PID, and s_currentProcess will hold the process handle (HANDLE type for Windows, and int type for Solaris). In order to read some parameters the process handle should be opened first, we will need to close the same process handle when the library shuts down (usually it will occur when JVM will be shut down for some reason).

Here is where JNI_OnUnload() function comes into play. However, some implementations of JVM actually do not call JNI_OnUnload(), and there occurs a danger that the handle will hang open forever. In order to decrease the probability of that, we should add a shutdown hook in the Java application that will explicitly call detachProcess() C function. Here is how we add a shutdown hook:

if (pid!=-1) {
            int result = SystemInformation.setPid(pid);
            if (result!=0) {
                return;
            }
            hasPid = true;
            // Create shutdown hook for proper process detach
            Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    SystemInformation.detachProcess();
                }
            });
        }

Some information about central processor can be pulled from the WinAPI GetSystemInfo() call. The most important value for the measurement process is the number of processors (s_numberOfProcessors), as long as it is the value by which CPU percentage will be adjusted. Win32 implementation of SystemInformation.getSysInfo() is quite tricky, because information about OS version, patches, service packs, and present hardware is being stored in a different way in each version of Windows. It is up to reader to analyze the corresponding source code and comments within the code. Here is a sample output from the code on Windows XP:

System.out.println("SysInfo: ”+SystemInformation.getSysInfo()):

SysInfo: WinXP Professional Service Pack 1 (Build 2600),
         on DBMOSWS2132 (Intel(R) Xeon(TM) CPU 1.70GHz)

And the same code on Solaris will give:

SysInfo: SunOS 5.8 sxav-dev Generic_108528-29 sun4u sparc
           SUNW,Ultra-Enterprise Sun_Microsystems

In order to get total time spent by process on the CPU we use the WinAPI function GetProcessTimes. Other function implementations are quite straightforward and use direct WinAPI calls, so there is not much use in discussing them. ListingD contains the make.bat file for the Windows version of GCC to help reader build the corresponding .dll library.

(Listing D)


Solaris implementation of the library is contained in Listing E and Listing F. These are both C files which should be compiled together into a shared library (.so). Helper make.sh for proper compiling a shared library is contained in Listing G. All Solaris-dependent system calls are moved into Listing-F, keeping Listing-E just a simple wrapper for JNI. Solaris implementation is more complicated than Win32 implementation, requiring more temporary data structures, kernel, and process tables. More comments are inside the listed code.

(Listing E)


(Listing F)


(Listing G)


In this article I showed you how to measure programmatically a Java process memory and CPU consumption. Of course a user can do the same by viewing a Windows Task Manager, or ps output. But the crucial point is that now a user can make any long-running and/or automated software performance tests, which is very important in developing a distributed, or scaleable, or real-time performance-critical software application. A nice addition is the ability to get a system software and hardware information, which can also be useful for developers. The library is implemented for Win32 and for Solaris platforms. However, it should not be a problem to port it to any other UNIX platform, for example, to Linux.

0 comments