How to profile threads in Java 5:
putting getThreadInfo() in a loop
On the previous page, we described two important calls to
ThreadMXBean: getAllThreadIds() and getThreadInfo().
Now we'll start to put these together in a background loop that will profile our
server or application. Our profiler will sit inside a Thread of its own
(we can actually define a Profiler class that extends Thread)
and its run() method will look something like this:
private volatile terminateRequest;
private final long profileInterval = 200;
private final int maxDepth = 5;
public void run() {
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
try {
while (!terminateRequest) {
long[] threadIds = threadBean.getAllThreadIds();
ThreadInfo[] infs = threadBean.getThreadInfo(threadIds, maxDepth);
for (ThreadInfo inf : infs) {
StackTraceElement[] str = inf.getStackTrace();
if (str == null)
continue;
StackTraceElement el = mostInterestingElement(str);
if (el != null)
incrementCountOf(el);
}
Thread.sleep(profileInterval);
}
} catch (InterruptedException iex) {
Thread.currentThread().interrupted();
throw new RuntimeException("Profiler interrupted");
}
}
This skeleton gives us a schema for periodically getting the stack traces
of all live threads, and for each stack trace incrementing the count of the
"most interesting element": that is, the line in the stack trace that for our
purposes tells us "where the thread is". For now, we haven't considered
synchronization issues (in a separate thread, we may want to read
the profile data collected!); we'll come back to that in a moment.
For deciding of a stack trace which is the "most interesting" line
that we want to count, then in the very simplest case, we could
take the top element. A slight improvement might be to take the top element
for which a line number is available:
private StackTraceElement mostInterestingElement(StackTraceElement[] st) {
for (int n = st.length,i=0; i < n; i++) {
StackTraceElement el = st[i];
if (el.getLineNumber() >= 0)
return el;
}
return null;
}
Now for our counts. The simplest thing to implement is to take a count of
number of times a thread is at a particular class/method/line combination, as
this is the information encapsulated by StackTraceElement. The
StackTraceElement handily implements hashCode() and
equals() for us, so we can create a simple hash map:
private static class ProfileInfo {
private int tickCount;
}
private Map<StackTraceElement,ProfileInfo> lineCounts =
new HashMap<StackTraceElement,ProfileInfo>(500);
private void incrementCountOf(StackTraceElement el) {
ProfileInfo inf = lineCounts.get(el);
if (inf == null) {
lineCounts.put(el, inf = new ProfileInfo());
}
inf.tickCount++;
}
I've defined a ProfileInfo class to hold our counts. If we want a simple
number, we could just use a plain old Integer; creating our own class means we
can potentially add other information such as CPU time if we want. (It also avoids the
ever so slight overhead of creating a new object on each update which would be the
case if we used Integer: although in any case, creating one new object per
thread on each iteration may be of minimal cost compared to that of actually getting
the thread stacks.)
After incrementing the corresponding counts for all the threads, we sleep for
a specified interval: in this example, roughly 200 milliseconds or 5 polls per
second. This interval is generally suitable for a moderately busy server. The sleep
interval is always approximate. The Thread.sleep() method, like methods
generally that put a thread in a wait state, can be interrupted and throw an
InterruptedException. (See the section on Java threads
and in particular the section on the behaviour of Thread.sleep().)
If that happened, there'd probably be nothing elegant
we wanted to do, but we're obliged to catch it because run() can't
throw a checked exception.
So we simply re-cast it as an unchecked RuntimeException.
If we put all the above together, we end up with a simple profiler that
sits dutifully gathering profile information. What it doesn't do is provide us
with a way to query the
profiling information,
which will entail some synchronization issues while profiling.
If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.
Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.