How to profile threads in Java 5
On the previous page, we gave a general introduction to
thread profiling methodology.
Now it's time to put things into practice. Recall that the basic thing that we
want to do is sit in a loop, periodically asking the JVM what all the threads
are doing. After getting the result (in the form of a series of stack traces), we need
to somehow assimilate this information, so that over time we get some kind of
percentage breakdown of where threads spend their time.
To query the JVM for thread information, we resort to the ThreadMXBean
class. This class is part of the Java Management and Instrumentation framework included
as standard in Java 5. We won't get bogged down in the details of this framework here,
except to note that various other instrumentation "beans" are available as standard, notably
the MemoryPoolMXBean which allows us to monitor usage of individual heaps
within the JVM, and GarbageCollectorMXBean, which allows us to track
overall time spend garbage collecting (arguably more granularity would actually have been
useful in this last case).
The management and instrumentation classes live inside the java.lang.management
package. Another class inside this package, ManagementFactory, provides us
with an entry point for obtaining management beans, in our case ThreadMXBean:
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
Now we have our ThreadMXBean, we can start asking it for some interesting
information. Two very important methods to us will be:
ThreadInfo getThreadInfo(long ids, int maxDepth);
The first of these methods returns an array containing all the internal
IDs of threads that are "live" at the point of calling the method. We use these
internal IDs to refer to the threads in other calls to ThreadMXBean, notably
the second of the above calls. The getThreadInfo() method returns us
an array of ThreadInfo objects, each of which encapsulates the state (including the
current stack trace) of the corresponding ID in the ids array passed in.
The maxDepth parameter allows us to specify the maximum lines of stack
trace to return for any one thread. The higher the number, the less chance that we'll
"miss" some information, but the more work we're potentially asking the JVM to do.
Tuning this parameter depends on our purpose. We may just want to know the top
entry, in which case we can pass in a value of 1. This is the simplest case, but
may not give us useful information in all cases. For example, if a thread is
currently inside BufferedInputStream.read(), that doesn't tell us which bit
of our code is calling the read method.
So in a moment, we'll consider a more
complex case where we want to know the topmost entry in the stack trace that
refers to a line in our code, as opposed to the Java class libraries.
Now we have the basic calls we need, we're ready to get some measurements
by putting getThreadInfo() in a loop.