There are several online resources that demonstrate Java client code that uses the Attach API. These include Daniel Fuchs's code listing for a JVMRuntimeClient, the "Setting up Monitoring and Management Programmatically" section of "Monitoring and Management Using JMX Technology," the Core Java Technology Tech Tip called "The Attach API," and the Javadoc API documentation for the class
com.sun.tools.attach.VirtualMachine
. These examples generally demonstrate using VirtualMachine.attach(String) to attach to the virtual machine based on its process ID in String form. This is generally followed by loading the appropriate agent with VirtualMachine.loadAgent(String), where the String parameter represents the path to the JAR file containing the agent. The VirtualMachine.detach() method can be called to detach from the previously attached JVM.All of the previously mentioned examples demonstrate use of the Attach API from Java clients. In this post, I demonstrate use of the Attach API via Groovy. The three code listings that follow present three pieces of Groovy code but all work together as a single script. The main script is embodied in the Groovy file
getJvmThreadInfo.groovy
and is a simple script file that calls the other two Groovy script files (attachToVirtualMachine.groovy
and displayMxBeanDerivedInfo.groovy
) to attach to the virtual machine and to display details regarding that virtual machine via its MXBeans.getJvmDetails.groovy
#!/usr/bin/env groovy // getJvmDetails.groovy // // Main script for extracting JVM details via Attach API and JMX. // Accepts single parameter which is the process ID (pid) of the Java application // whose JVM is to be connected to. // import static attachToVirtualMachine.retrieveConnector import static displayMxBeanDerivedInfo.* def serverConnection = attachToVirtualMachine.retrieveServerConnection(args[0]) displayMxBeanDerivedInfo.displayThreadInfo(serverConnection) displayMxBeanDerivedInfo.displayOperatingSystemInfo(serverConnection) displayMxBeanDerivedInfo.displayRuntimeInfo(serverConnection) displayMxBeanDerivedInfo.displayMemoryInfo(serverConnection)
attachToVirtualMachine.groovy
// attachToVirtualMachine.groovy // // Provide an MBeanServerConnection acquired via the Attach API. import javax.management.MBeanServerConnection import javax.management.remote.JMXConnector import javax.management.remote.JMXConnectorFactory import javax.management.remote.JMXServiceURL import com.sun.tools.attach.VirtualMachine /** * Provide an MBeanServerConnection based on the provided process ID (pid). * * @param pid Process ID of Java process for which MBeanServerConnection is * desired. * @return MBeanServerConnection connecting to Java process identified by pid. */ def static MBeanServerConnection retrieveServerConnection(String pid) { println "Get JMX Connector for pid ${pid}!" def connectorAddressStr = "com.sun.management.jmxremote.localConnectorAddress" def jmxUrl = retrieveUrlForPid(pid, connectorAddressStr) def jmxConnector = JMXConnectorFactory.connect(jmxUrl) return jmxConnector.getMBeanServerConnection() } /** * Provide JMX URL for attaching to the provided process ID (pid). * * @param @pid Process ID for which JMX URL is needed to connect. * @param @connectorAddressStr String for connecting. * @return JMX URL to communicating with Java process identified by pid. */ def static JMXServiceURL retrieveUrlForPid(String pid, String connectorAddressStr) { // Attach to the target application's virtual machine def vm = VirtualMachine.attach(pid) // Obtain Connector Address def connectorAddress = vm.getAgentProperties().getProperty(connectorAddressStr) // Load Agent if no connector address is available if (connectorAddress == null) { def agent = vm.getSystemProperties().getProperty("java.home") + File.separator + "lib" + File.separator + "management-agent.jar" vm.loadAgent(agent) // agent is started, get the connector address connectorAddress = vm.getAgentProperties().getProperty(connectorAddressStr) } return new JMXServiceURL(connectorAddress); }
displayMxBeanDerivedInfo.groovy
// displayMxBeanDerivedInfo.groovy // // Display details regarding attached virtual machine and associated MXBeans. import java.lang.management.ManagementFactory import java.lang.management.MemoryMXBean import java.lang.management.OperatingSystemMXBean import java.lang.management.RuntimeMXBean import java.lang.management.ThreadMXBean import javax.management.MBeanServerConnection /** * Display thread information based on ThreadMXBean associated with the provided * MBeanServerConnection. * * @param server MBeanServerConnection to use for obtaining thread information * via the ThreadMXBean. */ def static void displayThreadInfo(MBeanServerConnection server) { def remoteThreadBean = ManagementFactory.newPlatformMXBeanProxy( server, ManagementFactory.THREAD_MXBEAN_NAME, ThreadMXBean.class); println "Deadlocked Threads: ${remoteThreadBean.findDeadlockedThreads()}" println "Monitor Deadlocked Threads: ${remoteThreadBean.findMonitorDeadlockedThreads()}" println "Thread IDs: ${Arrays.toString(remoteThreadBean.getAllThreadIds())}" def threads = remoteThreadBean.dumpAllThreads(true, true); threads.each { println "\t${it.getThreadName()} (${it.getThreadId()}): ${it.getThreadState()}" } } /** * Display operating system information based on OperatingSystemMXBean associated * with the provided MBeanServerConnection. * * @param server MBeanServerConnection to use for obtaining operating system * information via the OperatingSystemMXBean. */ def static void displayOperatingSystemInfo(MBeanServerConnection server) { def osMxBean = ManagementFactory.newPlatformMXBeanProxy( server, ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME, OperatingSystemMXBean.class) println "Architecture: ${osMxBean.getArch()}" println "Number of Processors: ${osMxBean.getAvailableProcessors()}" println "Name: ${osMxBean.getName()}" println "Version: ${osMxBean.getVersion()}" println "System Load Average: ${osMxBean.getSystemLoadAverage()}" } /** * Display operating system information based on RuntimeMXBean associated with * the provided MBeanServerConnection. * * @param server MBeanServerConnection to use for obtaining runtime information * via the RuntimeMXBean. */ def static void displayRuntimeInfo(MBeanServerConnection server) { def remoteRuntime = ManagementFactory.newPlatformMXBeanProxy( server, ManagementFactory.RUNTIME_MXBEAN_NAME, RuntimeMXBean.class); println "Target Virtual Machine: ${remoteRuntime.getName()}" println "Uptime: ${remoteRuntime.getUptime()}" println "Classpath: ${remoteRuntime.getClassPath()}" println "Arguments: ${remoteRuntime.getInputArguments()}" } /** * Display operating system information based on MemoryMXBean associated with * the provided MBeanServerConnection. * * @param server MBeanServerConnection to use for obtaining memory information * via the MemoryMXBean. */ def static void displayMemoryInfo(MBeanServerConnection server) { def memoryMxBean = ManagementFactory.newPlatformMXBeanProxy( server, ManagementFactory.MEMORY_MXBEAN_NAME, MemoryMXBean.class); println "HEAP Memory: ${memoryMxBean.getHeapMemoryUsage()}" println "Non-HEAP Memory: ${memoryMxBean.getNonHeapMemoryUsage()}" }
The three Groovy code listings above together form a script that will use the Attach API to contact to an executing JVM without host or port specified and solely based on the provided process ID. The examples demonstrate use of several of the available MXBeans built into the virtual machine. Because it's Groovy, the code is somewhat more concise than its Java equivalent, especially because no checked exceptions must be explicitly handled and there is no need for explicit classes.
Much more could be done with the information provided via the Attach API and the MXBeans. For example, the Groovy script could be adjusted to persist some of the gathered details to build reports, Java mail could be used to alert individuals when memory constraints or other issues requiring notice occurred, and nearly anything else that can be done in Java could be added to these client scripts to make it easier to monitor and manage Java applications.
Running with the Attach API
The main implementation class of the Attach API,
VirtualMachine
, is located in the ${JAVA_HOME}\lib\tools.jar
or %JAVA_HOME\lib\tools.jar
JAR file included with the HotSpot SDK distribution. This file typically needs to be explicitly placed on the classpath of the Java client that uses the Attach API unless it is otherwise placed in a directory that is part of that inherent classpath. This is typically not required when using Groovy because it's normally already in Groovy's classpath. I briefly demonstrated this in the post Viewing Groovy Application's Classpath.Conclusion
The Attach API makes it easier for the Java (or Groovy) developer to write clients that can communicate with, manage, and monitor Java processes. The Attach API provides the same benefits to the developer of custom JMX clients that JConsole and VisualVM leverage.