
Java
MXBean Monitoring Servlet
Last updated Mar 24, 2006.Before leaving the topic of the new java.lang.management package and its MXBeans, I thought it would be fruitful to create a single configurable Servlet that exposes the pertinent information provided by the various MXBeans for a JVM that is running an application server. This includes threading information, memory information, and JVM versioning information.
In the last section we developed the MXBeanServlet that exposed thread information, so we use that as a base to start from. The first addition we make is with regard to JVM-specific version and runtime information. This is available through two MXBeans:
- OperatingSystemMXBean
- RuntimeMXBean
The OperatingSystemMXBean provides information about the OS: the architecture, the name of the OS, the version of the OS, and the number of processors available to the JVM. The RuntimeMXBean provides information about the JVM itself, such as the vendor name and version, the specification name and version, and runtime information such as the CLASSPATH, boot CLASSPATH, and library path. We group all of this information into an environment XML node with two subnodes: operating-system and runtime-system.
The next thing we add to the MXBeanServlet is support for memory information. Specifically we provide details about the heap and non-heap usage (initial, maximum, currently used, and peak usage), garbage collection occurrences (and their types), and memory pool configurations. By pooling for this information you can assess the number of minor and major garbage collections, the amount of time spent performing each, and the effectiveness of each in terms of collection usages.
Finally, the Servlet can be configured to run in one of the following modes:
- All: displays all information, including both configuration and runtime
- Runtime: displays runtime summary information, excluding configuration information such as OS information that will not change while the JVM is running
- Runtime-verbose: provides runtime information, including all runtime details such as individual thread activities
The Servlet mode is configured through request parameters passed to the Servlet upon invocation. Specifically the two request parameters are observed:
- mode=runtime|config|all, default is all
- verbose=true|false, defaults to true
Therefore to access the Servlet, here are possible URLs:
http://localhost:8080/mx/mx http://localhost:8080/mx/mx/?mode=runtime http://localhost:8080/mx/mx/?mode=runtime&verbose=false http://localhost:8080/mx/mx/?mode=all
The source code for the new MXBeanServlet is shown in listing 1.
Listing 1. MXBeanServlet.java
package com.javasrc.management;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import java.lang.management.*;
import org.jdom.*;
import org.jdom.output.*;
public class MXBeanServlet extends HttpServlet
{
public void init() throws ServletException
{
ServletContext ctx = getServletContext();
}
private Element getMemoryUsageElement( String name, MemoryUsage mu )
{
Element e = new Element( name );
if( mu != null )
{
e.setAttribute( "init", Long.toString( mu.getInit() ) );
e.setAttribute( "used", Long.toString( mu.getUsed() ) );
e.setAttribute( "committed", Long.toString( mu.getCommitted() ) );
e.setAttribute( "max", Long.toString( mu.getMax() ) );
}
return e;
}
public void service( HttpServletRequest req, HttpServletResponse res ) throws ServletException
{
try
{
System.out.println( "MXBeanServlet invoked..." );
Element root = new Element( "management-info" );
// Get our configuration information
boolean configMode = true;
boolean runtimeMode = true;
boolean verboseMode = true;
String mode = req.getParameter( "mode" );
if( mode != null )
{
if( mode.equalsIgnoreCase( "config" ) )
{
runtimeMode = false;
}
else if( mode.equalsIgnoreCase( "runtime" ) )
{
configMode = false;
}
}
String verboseStr = req.getParameter( "verbose" );
if( verboseStr != null && verboseStr.equalsIgnoreCase( "false" ) )
{
verboseMode = false;
}
// Get runtime information
RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
// Display environmental information
if( configMode )
{
OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean();
Element envElement = new Element( "environment" );
Element osElement = new Element( "operating-system" );
osElement.setAttribute( "architecture", os.getArch() );
osElement.setAttribute( "name", os.getName() );
osElement.setAttribute( "version", os.getVersion() );
osElement.setAttribute( "available-processors",
Integer.toString( os.getAvailableProcessors() ) );
envElement.addContent( osElement );
Element rtSystem = new Element( "runtime-system" );
rtSystem.setAttribute( "name", runtime.getName() );
rtSystem.setAttribute( "spec-name", runtime.getSpecName() );
rtSystem.setAttribute( "spec-vendor", runtime.getSpecVendor() );
rtSystem.setAttribute( "spec-version", runtime.getSpecVersion() );
rtSystem.setAttribute( "vm-name", runtime.getVmName() );
rtSystem.setAttribute( "vm-vendor", runtime.getVmVendor() );
rtSystem.setAttribute( "vm-version", runtime.getVmVersion() );
rtSystem.setAttribute( "management-spec-version", runtime.getManagementSpecVersion() );
rtSystem.setAttribute( "class-path", runtime.getClassPath() );
rtSystem.setAttribute( "boot-class-path", runtime.getBootClassPath() );
rtSystem.setAttribute( "library-path", runtime.getLibraryPath() );
envElement.addContent( rtSystem );
root.addContent( envElement );
}
// Display memory info
MemoryMXBean memorymbean = ManagementFactory.getMemoryMXBean();
Element memoryElement = new Element( "memory" );
memoryElement.addContent(
getMemoryUsageElement( "heap-usage", memorymbean.getHeapMemoryUsage() ) );
memoryElement.addContent(
getMemoryUsageElement( "non-heap-usage", memorymbean.getNonHeapMemoryUsage() ) );
// Read Garbage Collection information
Element gcsElement = new Element( "garbage-collectors" );
List<GarbageCollectorMXBean> gcmbeans = ManagementFactory.getGarbageCollectorMXBeans();
for( GarbageCollectorMXBean gcmbean : gcmbeans )
{
Element gcElement = new Element( "garbage-collector" );
gcElement.setAttribute( "name", gcmbean.getName() );
gcElement.setAttribute( "collection-count", Long.toString( gcmbean.getCollectionCount() ) );
gcElement.setAttribute( "collection-time", Long.toString( gcmbean.getCollectionTime() ) );
System.out.println( "Memory Pools: " );
String[] memoryPoolNames = gcmbean.getMemoryPoolNames();
for( int i=0; i<memoryPoolNames.length; i++ )
{
if( configMode )
{
Element memoryPoolElement = new Element( "memory-pool" );
memoryPoolElement.setAttribute( "name", memoryPoolNames[ i ] );
gcElement.addContent( memoryPoolElement );
}
}
gcsElement.addContent( gcElement );
}
memoryElement.addContent( gcsElement );
// Read Memory Pool Information
Element memoryPoolsElement = new Element( "memory-pools" );
List<MemoryPoolMXBean> mempoolsmbeans = ManagementFactory.getMemoryPoolMXBeans();
for( MemoryPoolMXBean mempoolmbean : mempoolsmbeans )
{
Element memoryPoolElement = new Element( "memory-pool" );
memoryPoolElement.setAttribute( "name", mempoolmbean.getName() );
memoryPoolElement.setAttribute( "type", mempoolmbean.getType().toString() );
memoryPoolElement.addContent( getMemoryUsageElement( "usage", mempoolmbean.getUsage() ) );
memoryPoolElement.addContent(
getMemoryUsageElement( "collection-usage", mempoolmbean.getCollectionUsage() ) );
memoryPoolElement.addContent(
getMemoryUsageElement( "peak-usage", mempoolmbean.getPeakUsage() ) );
String[] memManagerNames = mempoolmbean.getMemoryManagerNames();
for( int j=0; j<memManagerNames.length; j++ )
{
if( configMode )
{
Element memManagerElement = new Element( "memory-manager" );
memManagerElement.setAttribute( "name", memManagerNames[ j ] );
memoryPoolElement.addContent( memManagerElement );
}
}
memoryPoolsElement.addContent( memoryPoolElement );
}
memoryElement.addContent( memoryPoolsElement );
root.addContent( memoryElement );
// Display thread info
ThreadMXBean threads = ManagementFactory.getThreadMXBean();
Element threadsElement = new Element( "threads" );
threadsElement.setAttribute( "thread-count", Long.toString( threads.getThreadCount() ) );
threadsElement.setAttribute( "total-started-thread-count",
Long.toString( threads.getTotalStartedThreadCount() ) );
threadsElement.setAttribute( "daemon-thread-count",
Long.toString( threads.getDaemonThreadCount() ) );
threadsElement.setAttribute( "peak-thread-count", Long.toString( threads.getPeakThreadCount() ) );
long totalCpuTime = 0l;
long totalUserTime = 0l;
// Parse each thread
ThreadInfo[] threadInfos = threads.getThreadInfo( threads.getAllThreadIds() );
for( int i=0; i<threadInfos.length; i++ )
{
if( verboseMode )
{
Element threadElement = new Element( "thread" );
threadElement.setAttribute( "id", Long.toString( threadInfos[ i ].getThreadId() ) );
threadElement.setAttribute( "name", threadInfos[ i ].getThreadName() );
threadElement.setAttribute( "cpu-time-nano",
Long.toString( threads.getThreadCpuTime( threadInfos[ i ].getThreadId() ) ) );
threadElement.setAttribute( "cpu-time-ms",
Long.toString( threads.getThreadCpuTime( threadInfos[ i ].getThreadId() ) / 1000000l ) );
threadElement.setAttribute( "user-time-nano",
Long.toString( threads.getThreadUserTime( threadInfos[ i ].getThreadId() ) ) );
threadElement.setAttribute( "user-time-ms",
Long.toString( threads.getThreadUserTime( threadInfos[ i ].getThreadId() ) / 1000000l ) );
threadElement.setAttribute( "blocked-count",
Long.toString( threadInfos[ i ].getBlockedCount() ) );
threadElement.setAttribute( "blocked-time",
Long.toString( threadInfos[ i ].getBlockedTime() ) );
threadElement.setAttribute( "waited-count",
Long.toString( threadInfos[ i ].getWaitedCount() ) );
threadElement.setAttribute( "waited-time",
Long.toString( threadInfos[ i ].getWaitedTime() ) );
threadsElement.addContent( threadElement );
}
// Update our aggregate values
totalCpuTime += threads.getThreadCpuTime( threadInfos[ i ].getThreadId() );
totalUserTime += threads.getThreadUserTime( threadInfos[ i ].getThreadId() );
}
long totalCpuTimeMs = totalCpuTime / 1000000l;
long totalUserTimeMs = totalUserTime / 1000000l;
threadsElement.setAttribute( "total-cpu-time-nano", Long.toString( totalCpuTime ) );
threadsElement.setAttribute( "total-user-time-nano", Long.toString( totalUserTime ) );
threadsElement.setAttribute( "total-cpu-time-ms", Long.toString( totalCpuTimeMs ) );
threadsElement.setAttribute( "total-user-time-ms", Long.toString( totalUserTimeMs ) );
// Compute thread percentages
long uptime = runtime.getUptime();
threadsElement.setAttribute( "uptime", Long.toString( uptime ) );
double cpuPercentage = ( ( double )totalCpuTimeMs / ( double )uptime ) * 100.0;
double userPercentage = ( ( double )totalUserTimeMs / ( double )uptime ) * 100.0;
threadsElement.setAttribute( "total-cpu-percent", Double.toString( cpuPercentage ) );
threadsElement.setAttribute( "total-user-percent", Double.toString( userPercentage ) );
root.addContent( threadsElement );
// Output the XML to the caller
XMLOutputter outputter = new XMLOutputter( "\t", true );
outputter.output( root, res.getOutputStream() );
}
catch( Exception e )
{
e.printStackTrace();
throw new ServletException( e );
}
}
}
The MXBeanServlet uses JDOM to build its XML documents, which can be downloaded at http://www.jdom.org. And when packaging this Servlet, you need to include the following files:
/WEB-INF/web.xml /WEB-INF/classes/com/javasrc/management/MXBeanServlet.class /WEB-INF/lib/jdom.jar /WEB-INF/lib/xerces.jar
The following is sample output from the Servlet, configured to display all information in verbose mode:
<management-info>
<environment>
<operating-system architecture="x86" name="Windows XP"
version="5.1" available-processors="1" />
<runtime-system name="2396@shaines-hp" spec-name="Java Virtual Machine Specification"
spec-vendor="Sun Microsystems Inc." spec-version="1.0"
vm-name="Java HotSpot(TM) Client VM" vm-vendor="Sun Microsystems Inc."
vm-version="1.5.0_05-b05" management-spec-version="1.0"
class-path="C:\Program Files\Java\jdk1.5.0_05\lib\tools.jar;..."
boot-class-path="C:\jboss-4.0.3SP1\bin\\..\lib\endorsed\resolver.jar;..."
library-path="C:\Program Files\Java\jdk1.5.0_05\bin;..." />
</environment>
<memory>
<heap-usage init="0" used="38340704" committed="67887104" max="266403840" />
<non-heap-usage init="8585216" used="34991856" committed="35127296" max="100663296" />
<garbage-collections>
<garbage-collection name="Copy" collection-count="439" collection-time="1735">
<memory-pool name="Eden Space" />
<memory-pool name="Survivor Space" />
</garbage-collection>
<garbage-collection name="MarkSweepCompact" collection-count="38"
collection-time="14028">
<memory-pool name="Eden Space" />
<memory-pool name="Survivor Space" />
<memory-pool name="Tenured Gen" />
<memory-pool name="Perm Gen" />
</garbage-collection>
</garbage-collections>
<memory-pools>
<memory-pool name="Code Cache" type="Non-heap memory">
<usage init="196608" used="4971904" committed="4980736" max="33554432" />
<collection-usage />
<peak-usage init="196608" used="4974464" committed="4980736" max="33554432" />
<memory-manager name="CodeCacheManager" />
</memory-pool>
<memory-pool name="Eden Space" type="Heap memory">
<usage init="524288" used="2256336" committed="4325376" max="16580608" />
<collection-usage init="524288" used="0" committed="4325376" max="16580608" />
<peak-usage init="524288" used="4325376" committed="4325376" max="16580608" />
<memory-manager name="MarkSweepCompact" />
<memory-manager name="Copy" />
</memory-pool>
<memory-pool name="Survivor Space" type="Heap memory">
<usage init="65536" used="320648" committed="524288" max="2031616" />
<collection-usage init="65536" used="320648" committed="524288" max="2031616" />
<peak-usage init="65536" used="524288" committed="524288" max="2031616" />
<memory-manager name="MarkSweepCompact" />
<memory-manager name="Copy" />
</memory-pool>
<memory-pool name="Tenured Gen" type="Heap memory">
<usage init="1441792" used="35763720" committed="63037440" max="247791616" />
<collection-usage init="1441792" used="35763720"
committed="63037440" max="247791616" />
<peak-usage init="1441792" used="39277632" committed="63037440"
max="247791616" />
<memory-manager name="MarkSweepCompact" />
</memory-pool>
<memory-pool name="Perm Gen" type="Non-heap memory">
<usage init="8388608" used="30020024" committed="30146560" max="67108864" />
<collection-usage init="8388608" used="29919072"
committed="30146560" max="67108864" />
<peak-usage init="8388608" used="30194800"
committed="30408704" max="67108864" />
<memory-manager name="MarkSweepCompact" />
</memory-pool>
</memory-pools>
</memory>
<threads thread-count="104"
total-started-thread-count="114"
daemon-thread-count="90"
peak-thread-count="105"
total-cpu-time-nano="1484375000"
total-user-time-nano="1093750000"
total-cpu-time-ms="1484"
total-user-time-ms="1093"
uptime="320813"
total-cpu-percent="0.462574770972498"
total-user-percent="0.34069691689551235">
<thread id="119"
name="http-0.0.0.0-8080-2"
cpu-time-nano="140625000"
cpu-time-ms="140"
user-time-nano="125000000"
user-time-ms="125"
blocked-count="0"
blocked-time="-1"
waited-count="1"
waited-time="-1" />
<thread id="83"
name="Thread-39"
cpu-time-nano="15625000"
cpu-time-ms="15"
user-time-nano="15625000"
user-time-ms="15"
blocked-count="0"
blocked-time="-1"
waited-count="0"
waited-time="-1" />
</threads>
</management-info>
As you can see from this output, my laptop is running Windows XP with a single processor. I am running a Sun HotSpot JVM, version 1.5.0_05-b05. There were 439 copy garbage collections run and 38 MarkSweepCompact collections run. And there are 104 threads running, accounting for 0.46% CPU utilization.
Summary
The java.lang.management package provides a wealth of information through MXBeans that expose information about the internal behavior of the JVM itself. This information can be used to help you assess the health of the JVM and can serve as an early warning mechanism to help you avoid performance problems before your users are affected by them. Deploy this WAR file to your favorite application server or Servlet container and see how you fare!