Wednesday 3 August 2011

Troubleshoot Java CPU Hog

I am not scared of the CPU hog in the application which is developed with c(c++). Using WinDBG, you can track down the processor hog by its detailed instructions. What about Java application, especially the webapp you developed and it is hosted by TOMCAT? Recently,I was asked to have a look of tomcat high CPU problem, it forced me to learn some JAVA debug and profile skill. Fortunately, with JDK 1.6, it is possible to use java.lang.management.ManagementFactory.getThreadMXBean() to pin down the offensive thread, then print out its StackTrace.


Please save the following code(updated) to a file called "cpusage.jsp", and place it into the subfolder tomcat\webapps\ROOT. Once you notice a hign CPU of tomcat service, then launch the browser and visit http://localhost:8080/cpusage.jsp(you may need to change the port). Refresh the page after 2-3 seconds, it will list all active threads, and you can click the hyperlink to see the StackTrace of the related thread. Please read the pdf version to see a detailed demo.





<%@ page import="java.lang.management.*, java.util.*" %>
<%!
Map cpuTimes = new HashMap();
Map cpuTimeFetch = new HashMap();
%>
<%
out.println("<h1><center>Tomcat CPU Profiler</center></h1>");

long cpus = Runtime.getRuntime().availableProcessors();
out.print("<p>The number of processors available to JVM = " + cpus + "</p>");
ThreadMXBean threads = ManagementFactory.getThreadMXBean();
long now = System.currentTimeMillis();
ThreadInfo[] t = threads.dumpAllThreads(false, false);

out.print("<p>Currently it has " + t.length + " threads, below only the active(CPU percentage > 0) threads are listed.</p>");
out.print("<p>The data format is: (thread index) | (thread name) (cpu percentage)</p>");

for (int i = 0; i < t.length; i++)
{
long id = t[i].getThreadId();
Long idid = new Long(id);
long current = 0;
if (cpuTimes.get(idid) != null)
{
long prev = ((Long) cpuTimes.get(idid)).longValue();
current = threads.getThreadCpuTime(t[i].getThreadId());
long catchTime = ((Long) cpuTimeFetch.get(idid)).longValue();
double percent = (current - prev) / ((now - catchTime) * cpus * 10000);
if (percent > 0 && prev > 0)
{
out.println("<li>" + i + " | <a href=cpusage.jsp?ti=" + i +">"+ t[i].getThreadName() + "</a>     " + percent);
}
}
cpuTimes.put(idid, new Long(current));
cpuTimeFetch.put(idid, new Long(now));
}

if (request.getParameter("ti") != null)
{
int index = Integer.parseInt(request.getParameter("ti"));

StackTraceElement[] stackElements = t[index].getStackTrace();

out.println("<p>The stack for thread <i>" + t[index].getThreadName() + "</i> :</p>");
out.println("<table><tr><td>Line number</td><td>Full class name</td><td>Method name</td></tr>");

for (int lcv = 0; lcv < stackElements.length; lcv++)
{
out.println("<tr>");
out.println("<td>" + stackElements[lcv].getLineNumber() + "</td>");
String className = stackElements[lcv].getClassName();
out.println("<td>" + className + "</td>");
out.println("<td>" + stackElements[lcv].getMethodName() + "</td>");
out.println("</tr>");

}
out.println("</table>");
}

%>