I'm a software architect/consultant in Boulder, Colorado. I started blogging at: http://wayne-adams.blogspot.com/, but have since started the new blog, Data Sceintist in Training which will cover the areas I am learning as part of my own "big data" training, including the Hadoop family, frameworks like OpenStack, the R programming language, column-oriented databases and the art of examining data sets for useful patterns. The original blog will be kept alive with Java-specific posts. Wayne is a DZone MVB and is not an employee of DZone and has posted 35 posts at DZone. You can read more from them at their website. View Full User Profile

Java Profiling: Under the Covers

12.01.2009
| 30944 views |
  • submit to reddit

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:

    1. Obtain a descriptor for the target JVM.

    2. Obtain a VirtualMachine instance with the static VirtualMachine.attach()method.

    3. Load your profiling agent into the target JVM with the VirtualMachine.loadAgent()method.

    4. 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:

try
{
VirtualMachine vm = VirtualMachine.attach(pid);
// load agent into target VM
vm.loadAgent(agentPath);
// detach
vm.detach();
}
catch (AgentInitializationException aie)
{
System.err.println("AgentInitializationException: " + aie.getMessage());
aie.printStackTrace();
}
catch (AgentLoadException ale)
{
System.err.println("AgentLoadException: " + ale.getMessage());
ale.printStackTrace();
}
catch (AttachNotSupportedException anse)
{
System.err.println("AttachNotSupportedException: " + anse.getMessage());
anse.printStackTrace();
}
catch (IOException ioe)
{
System.err.println("IOException: " + ioe.getMessage());
ioe.printStackTrace();
}

 
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.

Final Thoughts

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!


Published at DZone with permission of Wayne Adams, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Eric Meallier replied on Tue, 2009/12/08 - 6:13am

hard to get this working !

 

during the instrumentation, a RuntimeException was throwed by getMethods() call. The problem comes from the naming convention of the classes.

The classname given by transform is like this test/Test. The classpool understand this syntax but store test.Test. When you are looking for the class (getClass() just before getMethods) the class is lazy loaded (the byte code is not introscepted) but is not good. the problem only appear when the getMethods is called.

A workaround is to call classPool.get(className.replace('/', '.'));

 

Thanks for the article.

Victor Tsoukanov replied on Fri, 2010/02/19 - 1:14am

If you do not want to pay money for profiling tool, there is a free and quite suitable profiler – Netbeans profiler

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.