Java Profiling: Under the Covers
Attaching to a Running Application
I mentioned earlier that this functionality, while supported, is implementation dependent. In this section, I'll point you to the information you need to attach to a Sun HotSpot JVM.
If you've used JDK tools like Java VisualVM or JConsole, you've seen the attach API at work. If you're going to attach to a running process, you need to get a handle for it (a VirtualMachineDescriptor, specifically), which implies a method for listing processes (see VirtualMachine.list()). These are the process descriptors you see in the above tools' connect dialogs at launch time. The best place to start is to look at the API documentation for com.sun.tools.attach. This information can be found in the tools documentation of your JDK documentation download, or online. From there, take a look at the documentation for the com.sun.tools.attach.VirtualMachine class -- it provides an outline for getting started. I'll start with this, pointing out some of the issues you can run into along the way.
The basic process is outlined below:
Create a public static void agentmain() method in your profiling agent (if you have already created a launch-time profiling agent, this can contain the same logic as your premain()method).
Add an Agent-Class: attribute to your profiler's manifest file, specifying the fully-qualified name of your profiler class.
In your launch application, perform the following steps:
Obtain a descriptor for the target JVM.
Obtain a VirtualMachine instance with the static VirtualMachine.attach()method.
Load your profiling agent into the target JVM with the VirtualMachine.loadAgent()method.
Detach from the target JVM.
At this point, your profiler's agentmain() method will be called and you are in business.
An example of code that would perform these steps follows:
VirtualMachine vm = VirtualMachine.attach(pid);
// load agent into target VM
catch (AgentInitializationException aie)
System.err.println("AgentInitializationException: " + aie.getMessage());
catch (AgentLoadException ale)
System.err.println("AgentLoadException: " + ale.getMessage());
catch (AttachNotSupportedException anse)
System.err.println("AttachNotSupportedException: " + anse.getMessage());
catch (IOException ioe)
System.err.println("IOException: " + ioe.getMessage());
While I don't provide a fully-functional example profiler here that uses the attach API, the above code is taken from the open-source MonkeyWrench profiler (which will be discussed in a future article). For more details on possible issues that can arise while using this API (and solutions!) please see my post "Instrumenting running Java processes with the com.sun.tools.attach API" at http://wayne-adams.blogspot.com/2009/07/instrumenting-running-java-processes.html.
Now that you have the basic framework of a profiler, here are some observations and ideas.
As I alluded to earlier, you can get a reference to the ThreadMXBean on your method entry and exit notifications. Since you can get the ID of the current thread, you can query the MXBean and retrieve its ThreadInfo object and other properties. This will give you the thread's accumulated CPU time and the stack trace, among other things. If you want to profile CPU time, you should keep in mind that you'll need the full method or constructor signature (parameters included), class name, and thread ID to uniquely identify the trace of execution in and out of a method. Also, to correctly handle recursive method calls, when you match an exit notification with an entry notification, you'll have to unroll the pending method entry notifications by most-recent-entry first, in order to match the correct entry with the exit.
The stack trace information is useful because it allows you to build profiles of the paths used to reach the method or constructor you're profiling. These profiles are called "sites" in hprof, the very useful but rather expensive profiling agent provided with the JDK. Your profiler can maintain a list of unique stack traces for each method invocation and provide a summary of the sites and their relative frequency as execution progresses.
If you're profiling constructor activity and are interested in the sizes of objects being instantiated, the Instrumentation class has a getObjectSize() method. The API docs say that this size is implementation-dependent and an approximation. The size appears to be a shallow-copy object size.
This concludes our survey of the instrumentation API provided by the JDK. While a compact package, java.lang.instrument, in conjunction with a good bytecode-injection library, provides everything you need to get started writing a profiler of your own. Even if you don't find yourself in the position of needing to write a profiler, knowing a little about how to do so will no doubt make you appreciate Java VisualVM or hprof -- or your licensed commercial profiler -- a little more than you did before!
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)