Wednesday 3 July 2013

When one JVM process is not enough

I have just released to public domain batch of few interesting exercises in process spawning (fork) in Java, JVM process discovery, inter process communication, MBeans, JMX, Jvmstat, Java Attach API and handling child process output.

https://bitbucket.org/espinosa/a-java-playground/src/321b1c2f1c04b30ba7810ea76b89101091a931cf/src/main/java/my/code/a003/process?at=master

Have you ever wonder how JDK tools like VisualVM , JPS and JConsole discover all local running applications? How they manage to get hold of that internal information about them?

Featured exercises 

Unfinished

Get full command line for current and (external) target JVM process the same way VisualVM gets them. I'm nearly there, just unfinished due to lack of time. Simple sun.java.command system property provides only basic arguments like main class name and following arguments but not JVM attributes, like -D... or tuning -X: and -XX:
See: A021PrintActualExecutedCommandUsingSystemProperty.java and A022PrintActualExecutedCommandUsingRuntimeMXBean.java.

Lessons learned

There is no generic pure Java way how to get PID of started child process. If it is non JVM process then you are out of luck.

There is no straightforward way how to get PID of started child JVM process, however if it is a JVM application, one can use monitoring and attaching API from tools.jar distributed with JDK, but not JRE. tools.jar cannot be obtained from Maven, it has to come from the Java distribution.

There is no generic way how to kill non-java process. If it is a child process and you still hold reference to process, you can use Process#destroy() If it is not and the target process in not JVM, you are out of luck.

There is not easy straightforward way how to kill a process even if it is JVM application. There is no API call for that, be it Jvmstat or Attach or JMXBean. the only chance is to inject extra behaviour, using Java Agents, like MBean capable of shutting application down and capable of being operated remotely.

There is no easy, straightforward, way how to connect to a JVM application using JMX if you know the PID of the target application. To establish JMX connection PID has to be converted to Local Connector Address, then converted to JMXServiceURL. To get Local Connector Address from PID is tricky and involves loading management agent to target application (management-agent.jar) using Attach API. Agent then exposes com.sun.management.jmxremote.localConnectorAddress. A bit hackish but it is the official way recommended by Oracle (in Bugzilla).

There was no easy way how to redirect child process output to current stdout prior Java 7.

In Java 6+ applications you don't have to set up JMXBean server, it is capable of discovery and response to client JMXBean calls. You can get a lot of runtime information this way. To expose more part of running JVM app you have to load Java Agent, like management-agent.jar. Yep, you are loading new behaviour to unsuspected application!

Stopping child process, even when you have Process reference to it, and using standard Process#destroy() can still fail if the spawned child process spawned process another process itself (that is grand-children process). This may be issues at least on some (Windows) platforms.
See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4770092.

Java Agents carry a great potential. You can instrument application to do things not originally supposed to do, not only using special startup parameters, like with AOP LTW (Load Time Weaving), but also for already running application, completely unsuspecting JVM process! What are the security implications?

Useful links

I would get nowhere if those guys has not shared their wisdom, (in random order) :

VisualVM sources
  • com.sun.tools.visualvm.application.jvm.Jvm#ApplicationSupport#createCurrentApplication()
  • com.sun.tools.visualvm.jvmstat.application.JvmstatApplicationProvider#processNewApplicationsByPids()}
  • com.sun.tools.visualvm.jvmstat.application.JvmstatApplicationProvider#registerJvmstatConnection()
JPS sources