Connecting to a JVM programmatically

A JVM has a lot of information which can be enclosed by JMX. But how to get this information from a JVM and how to find the JVM’s running on a system?

This blog describes how this can be done by code running on the same system as the JVM’s from which information is needed.

Find and attach JVM’s




Connecting to a JVM can be done by the attach API. The tools.jar provides the package com.sun.tools.attach containing the attach api. Using the class VirtualMachine a program is able to:


  • Get a list of available JVM’s on the system

  • Attach to a JVM

  • Detach from a JVM

  • Loading an agent into the attached JVM

  • Reading the system properties from the attached JVM

  • Reading the agent properties from the attached JVM





The static method VirtualMachine.list() returns a list with VirtualMachineDescriptor instances. A VirtualMachineDescriptor represents a JVM found on the machine it’s called on and contains the process id and a name of the JVM. The name is usally the command line which started the JVM. Attaching to a JVM makes it possible to read properties or load an agent for that specific JVM. The code snippet below shows how all JVM’s are listed and a system property can be read.




public class Attach {
private void showData() {
List<VirtualMachineDescriptor> vms = VirtualMachine.list();
for (VirtualMachineDescriptor virtualMachineDescriptor : vms) {
logger.info("============ Show JVM: pid = ", virtualMachineDescriptor.id(), virtualMachineDescriptor.displayName());
VirtualMachine virtualMachine = attach(virtualMachineDescriptor);
if (virtualMachine != null) {
logger.info(" Java version = " + readSystemProperty(virtualMachine, "java.version"));
}
}
}

private VirtualMachine attach(VirtualMachineDescriptor virtualMachineDescriptor) {
VirtualMachine virtualMachine = null;
try {
virtualMachine = VirtualMachine.attach(virtualMachineDescriptor);
} catch (AttachNotSupportedException anse) {
logger.error("Couldn't attach", anse);
} catch (IOException ioe) {
logger.error("Exception attaching or reading a jvm.", ioe);
} finally {
detachSilently(virtualMachine);
}
return virtualMachine;
}

private String readSystemProperty(VirtualMachine virtualMachine, String propertyName) {
String propertyValue = null;
try {
Properties systemProperties = virtualMachine.getSystemProperties();
propertyValue = systemProperties.getProperty(propertyName);
} catch (IOException e) {
logger.error("Reading system property failed", e);
}
return propertyValue;
}

private void detachSilently(VirtualMachine virtualMachine) {
if (virtualMachine != null) {
try {
virtualMachine.detach();
} catch (IOException e) {
e.printStackTrace();
}
};
}
}



Note that only JVM’s can be found that are started by the same user or by a user that belongs to the same group.



Load the management agent



Being enable to attach to a JVM is nice, but not very useful if noting could be done besides reading properties. A JVM does enable Monitoring and Management Using JMX Technology. To make an JMX connection a URL is needed. The JMX URL is available as an agent property if the management agent is loaded. The code snippet below is a modified from the previous example code which is not changed is left out the code snippet. The code snippet shows how to read the URL from the agent properties. If the property isn’t found then the management agent is loaded and again the agent property for the JMX URL is called to get the URL.




public class Attach {
private void showData() {
List<VirtualMachineDescriptor> vms = VirtualMachine.list();
for (VirtualMachineDescriptor virtualMachineDescriptor : vms) {
logger.info("============ Show JVM: pid = ", virtualMachineDescriptor.id(), virtualMachineDescriptor.displayName());
VirtualMachine virtualMachine = null;
try {
virtualMachine = attach(virtualMachineDescriptor);
if (virtualMachine != null) {
String jmxUrl = getJmxUrl(virtualMachine);
readDataWithJmx(jmxUrl);
}
} finally {
detachSilently(virtualMachine);
}

}
}

private String getJmxUrl(VirtualMachine virtualMachine) {
String jmxUrl = readAgentProperty(virtualMachine, "com.sun.management.jmxremote.localConnectorAddress");
if (jmxUrl == null) {
loadMangementAgent(virtualMachine);
jmxUrl = readAgentProperty(virtualMachine, "com.sun.management.jmxremote.localConnectorAddress");
}
logger.info("JMX URL found = {}", jmxUrl);
return jmxUrl;
}

private void loadMangementAgent(VirtualMachine virtualMachine) {
final String id = virtualMachine.id();
String agent = null;
Boolean loaded = false;
try {
String javaHome = readSystemProperty(virtualMachine, "java.home");
agent = javaHome + "/lib/management-agent.jar";
virtualMachine.loadAgent(agent);
loaded = true;
} catch (IOException e) {
logger.error("Reading system properties or loading the agent resulted in a exception for pid " + id, e);
} catch (AgentLoadException e) {
logger.error("Loading agent failed for pid " + id, e);
} catch (AgentInitializationException e) {
logger.error("Agent initialization failed for pid " + id, e);
}
logger.info("Loading management agent \" succeeded = );
}

private String readAgentProperty(VirtualMachine virtualMachine, String propertyName) {
String propertyValue = null;
try {
Properties agentProperties = virtualMachine.getAgentProperties();
propertyValue = agentProperties.getProperty(propertyName);
} catch (IOException e) {
logger.error("Reading agent property failed", e);
}
return propertyValue;
}
}


Make a JMX connecting to a JVM




The previous code example shows how to get the JMX URL from a VirtualMachine. With this URL it is possible to create a connection to the JMXServer and use the JMX beans registered at the JMXServer. The following code snippets shows how to create a JMX connection and how to get a JMX bean from the JMXServer. It also shows how to use the javax.management.NotificationListener.




public class Attach {
private void showData() {
List<VirtualMachineDescriptor> vms = VirtualMachine.list();
for (VirtualMachineDescriptor virtualMachineDescriptor : vms) {
logger.info("============ Show JVM: pid = ", virtualMachineDescriptor.id(), virtualMachineDescriptor.displayName());
VirtualMachine virtualMachine = null;
try {
virtualMachine = attach(virtualMachineDescriptor);
if (virtualMachine != null) {
String jmxUrl = getJmxUrl(virtualMachine);
readDataWithJmx(jmxUrl);
}
} finally {
detachSilently(virtualMachine);
}

}
}

private void readDataWithJmx(String jmxUrl) {
JvmMxBean mxBean = new JvmMxBean(jmxUrl);
try {
if (mxBean.connect()) {
RuntimeMXBean runtimeMXBean = mxBean.getRuntimeMXBean();
logger.info("Jvm uptime = {}", runtimeMXBean.getUptime());
OperatingSystemMXBean operatingSystemMXBean = mxBean.getOperatingSystemMXBean();
logger.info("available processors = {}", operatingSystemMXBean.getAvailableProcessors());
}
} finally {
mxBean.disconnect();
}
}
}



public class JvmMxBean implements NotificationListener {

private Logger logger = LoggerFactory.getLogger(JvmMxBean.class);

private final String jmxUrl;

private MBeanServerConnection mBeanServerConnection;
private JMXConnector connector;

private RuntimeMXBean runtimeMXBean;
private OperatingSystemMXBean operatingSystemMXBean;

public JvmMxBean(final String jmxUrl) {
super();
this.jmxUrl = jmxUrl;
}

public RuntimeMXBean getRuntimeMXBean() {
if (mBeanServerConnection != null && runtimeMXBean == null) {
runtimeMXBean = getMxBean(ManagementFactory.RUNTIME_MXBEAN_NAME, RuntimeMXBean.class);
}
return runtimeMXBean;
}

public synchronized OperatingSystemMXBean getOperatingSystemMXBean() {
if (mBeanServerConnection != null && operatingSystemMXBean == null) {
operatingSystemMXBean = getMxBean(ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME, OperatingSystemMXBean.class);
}
return operatingSystemMXBean;
}

public boolean connect() {
try {
JMXServiceURL url = new JMXServiceURL(jmxUrl);
connector = JMXConnectorFactory.newJMXConnector(url, null);
connector.addConnectionNotificationListener(this, null, jmxUrl);
connector.connect();
mBeanServerConnection = connector.getMBeanServerConnection();
return true;
} catch (Exception e) {
disconnect();
throw new IllegalStateException("Couldn't connect to JVM with URL: " + jmxUrl, e);
}
}

public boolean disconnect() {
boolean result = false;
try {
if (connector != null) {
connector.removeConnectionNotificationListener(this);
connector.close();
}
result = true;
} catch (ListenerNotFoundException e) {
logger.debug("Removing the listener from the connector resulted in an exception, but is ignored.", e);
result = true;
} catch (Exception e) {
logger.error("Closing the JMX connection failed.", e);
throw new IllegalStateException("Closing the connection failed for JVM", e);
} finally {
mBeanServerConnection = null;
connector = null;
}
return result;
}

@Override
public void handleNotification(Notification notification, Object handback) {
final JMXConnectionNotification noti = (JMXConnectionNotification) notification;
if (!handback.equals(jmxUrl)) {
return;
}
logger.info("Handling \"{}\" noitification from MBeanServer", noti.toString());
if (noti.getType().equals(JMXConnectionNotification.CLOSED)) {
disconnect();
} else if (noti.getType().equals(JMXConnectionNotification.FAILED)) {
disconnect();
} else if (noti.getType().equals(JMXConnectionNotification.NOTIFS_LOST)) {
disconnect();
}
}

private <MX> MX getMxBean(String mxBeanName, Class<MX> mxBeanInterfaceClass) {
MX result = null;
if (mBeanServerConnection != null) {
try {
result = ManagementFactory.newPlatformMXBeanProxy(mBeanServerConnection, mxBeanName, mxBeanInterfaceClass);
} catch (IOException ioe) {
logger.error("A communication problem occured with jvm", ioe);
} catch (IllegalArgumentException iae) {
logger.error("A configuration problem resulted in an exception for jvm", iae);
}
}
return result;
}
}


Final words



This blog gives a brief explanation how to find JVM’s on a local system and how to use JMX to read data from that JVM using the standard management agent. The examples will work on the standard JDK installations from Java 5. But for Java 5 the system property com.sun.management.jmxremote without a value is needed to enable JMX. Java 6 and 7 will work out of the box. This article does not take into account how to deal with security when to attach to a JVM or read data from a JVM with JMX. The links below gives also some hints on those issues.



Links



  • http://docs.oracle.com/javase/6/docs/jdk/api/attach/spec/index.html

  • http://docs.oracle.com/javase/6/docs/technotes/guides/management/agent.html