A JMX client uses the Attach API to dynamically attach to a target virtual machine and load the JMX agent (if it is not already loaded) from the management-agent.jarfile, which is located in the libsubdirectory of the target virtual machine’s JRE home directory.
Listing 7-1 presents a simple thread information viewer application that takes care of these tasks and communicates with the JMX agent.
Listing 7-1.ThreadInfoViewer.java
// ThreadInfoViewer.java;
// Unix compile : javac -cp $JAVA_HOME/lib/tools.jar ThreadInfoViewer.java //
// Windows compile: javac -cp %JAVA_HOME%/lib/tools.jar ThreadInfoViewer.java import static java.lang.management.ManagementFactory.*;
import java.lang.management.*;
import java.io.*;
import java.util.*;
import javax.management.*;
Table 7-2.Continued
Method Description
import javax.management.remote.*;
import com.sun.tools.attach.*;
public class ThreadInfoViewer {
static final String CON_ADDR =
"com.sun.management.jmxremote.localConnectorAddress";
public static void main (String [] args) throws Exception {
if (args.length != 1) {
System.err.println ("Unix usage : "+
"java -cp $JAVA_HOME/lib/tools.jar:. "+
"ThreadInfoViewer pid");
System.err.println ();
System.err.println ("Windows usage: "+
"java -cp %JAVA_HOME%/lib/tools.jar;. "+
"ThreadInfoViewer pid");
return;
}
// Attempt to attach to the target virtual machine whose identifier is // specified as a command-line argument.
VirtualMachine vm = VirtualMachine.attach (args [0]);
// Attempt to obtain the target virtual machine's connector address so // that this virtual machine can communicate with its connector server.
String conAddr = vm.getAgentProperties ().getProperty (CON_ADDR);
// If there is no connector address, a connector server and JMX agent // are not started in the target virtual machine. Therefore, load the // JMX agent into the target.
if (conAddr == null) {
// The JMX agent is stored in management-agent.jar. This JAR file // is located in the lib subdirectory of the JRE's home directory.
String agent = vm.getSystemProperties ()
.getProperty ("java.home")+File.separator+
"lib"+File.separator+"management-agent.jar";
// Attempt to load the JMX agent.
vm.loadAgent (agent);
// Once again, attempt to obtain the target virtual machine's // connector address.
conAddr = vm.getAgentProperties ().getProperty (CON_ADDR);
// Although the second attempt to obtain the connector address // should succeed, throw an exception if it does not.
if (conAddr == null)
throw new NullPointerException ("conAddr is null");
}
// Prior to connecting to the target virtual machine's connector // server, the String-based connector address must be converted into a // JMXServiceURL.
JMXServiceURL servURL = new JMXServiceURL (conAddr);
// Attempt to create a connector client that is connected to the // connector server located at the specified URL.
JMXConnector con = JMXConnectorFactory.connect (servURL);
// Attempt to obtain an MBeanServerConnection that represents the // remote JMX agent's MBean server.
MBeanServerConnection mbsc = con.getMBeanServerConnection ();
// Obtain object name for thread MBean, and use this name to obtain the // name of the thread MBean that is controlled by the JMX agent's MBean // server.
ObjectName thdName = new ObjectName (THREAD_MXBEAN_NAME);
Set<ObjectName> mbeans = mbsc.queryNames (thdName, null);
// The for-each loop conveniently returns the name of the thread MBean.
// There should only be one iteration because there is only one thread // MBean.
for (ObjectName name: mbeans) {
// Obtain a proxy for the ThreadMXBean interface that forwards its // method calls through the MBeanServerConnection identified by // mbsc.
ThreadMXBean thdb;
thdb = newPlatformMXBeanProxy (mbsc, name.toString (), ThreadMXBean.class);
// Obtain and output thread information.
System.out.println ("Threads presumably still alive...");
long [] thdIDs = thdb.getAllThreadIds ();
if (thdIDs != null) // safety check (possibly unnecessary) for (long thdID: thdIDs)
{
ThreadInfo thdi = thdb.getThreadInfo (thdID);
System.out.println (" Name: "+thdi.getThreadName ());
System.out.println (" State: "+thdi.getThreadState ());
}
// The information identifies any deadlocked threads...
System.out.println ("Deadlocked threads...");
thdIDs = thdb.findDeadlockedThreads ();
if (thdIDs == null)
System.out.println (" None");
else {
ThreadInfo [] thdsi = thdb.getThreadInfo (thdIDs);
for (ThreadInfo thdi: thdsi)
System.out.println (" Name: "+thdi.getThreadName ());
} } } }
ThreadInfoViewerdemonstrates the kinds of things that a JMX client does to commu- nicate with a target virtual machine’s JMX agent. Notice the call to getAgentProperties(), followed by a call to getProperty(), to determine if the com.sun.management.jmxremote.
localConnectorAddressproperty (as specified via constant CON_ADDR) is present. If this property is not present, no JMX agent and connector server are running in the target.
You’ll find Windows and Unix instructions for compiling ThreadInfoViewer.javanear the top of the source code. tools.jarmust be in the classpath so the compiler can locate the Attach API. Following a successful compilation, you’ll need a suitable application to try out this new JMX client. Consider the buggy threading application, whose source code appears in Listing 7-2.
Listing 7-2.BuggyThreads.java
// BuggyThreads.java public class BuggyThreads {
public static void main (String [] args) {
System.out.println ("Starting Thread A");
new ThreadA ("A").start ();
System.out.println ("Starting Thread B");
new ThreadB ("B").start ();
System.out.println ("Entering infinite loop");
while (true);
} }
class ThreadA extends Thread {
ThreadA (String name) {
setName (name);
}
public void run () {
while (true) {
synchronized ("A") {
System.out.println ("Thread A acquiring Lock A");
synchronized ("B") {
System.out.println ("Thread A acquiring Lock B");
try {
Thread.sleep ((int) Math.random ()*100);
}
catch (InterruptedException e) {
}
System.out.println ("Thread A releasing Lock B");
}
System.out.println ("Thread A releasing Lock A");
} } } }
class ThreadB extends Thread {
ThreadB (String name) {
setName (name);
}
public void run () {
while (true) {
synchronized ("B") {
System.out.println ("Thread B acquiring Lock B");
synchronized ("A") {
System.out.println ("Thread B acquiring Lock A");
try {
Thread.sleep ((int) Math.random ()*100);
}
catch (InterruptedException e) {
}
System.out.println ("Thread B releasing Lock A");
}
System.out.println ("Thread B releasing Lock B");
} } } }
Compile the source code and run BuggyThreadsin one command window (no extra libraries are required to run this application). Open a second command window and run jpsto obtain the process identifier for BuggyThreads. Using this identifier, invoke ThreadInfoViewer(for example, java -cp %JAVA_HOME%/lib/tools.jar;. ThreadInfoViewer 1932). After a few moments, you should observe output similar to the following:
Threads presumably still alive...
Name: JMX server connection timeout 15 State: RUNNABLE
Name: JMX server connection timeout 14 State: TIMED_WAITING
Name: RMI Scheduler(0) State: TIMED_WAITING
Name: RMI TCP Connection(2)-xxx.xxx.xxx.xxx State: RUNNABLE
Name: RMI TCP Accept-0 State: RUNNABLE Name: B
State: BLOCKED Name: A State: BLOCKED Name: Attach Listener State: RUNNABLE
Name: Signal Dispatcher State: RUNNABLE
Name: Finalizer State: WAITING
Name: Reference Handler State: WAITING
Name: main State: RUNNABLE Deadlocked threads...
Name: B Name: A
After reviewing the output, it comes as no surprise that the main thread is still run- ning because it is in an infinite loop. It is also no surprise to discover that threads Aand B are deadlocked. At some point in each thread’s execution, the thread acquired a lock and then was forced to wait while attempting to acquire a second lock, which was already held by its counterpart thread.
■ Note Alan Bateman, Sun’s specification lead on JSR 203: More New I/O APIs for the Java Platform, pres- ents MemVieweras another example of a JMX client that works with the Attach API in his “Another piece of the tool puzzle” blog entry (http://blogs.sun.com/alanb/entry/another_piece_of_the_tool).