In the previous blog entry Classloader leaks: the dreaded "java.lang.OutOfMemoryError: PermGen space" exception I explained how this type of problem can originate in the application code that you deploy to an application server. In this post I'll explain how to track down the leak so that you can fix it.
Profilers
Memory leak? Use a profiler. Right? Well... generally speaking the answer is yes, but classloader leaks are a bit special...
To refresh your memory (pardon the pun), a memory leak is an object that the system unintentionally hangs on to, thereby making it impossible for the garbage collector to remove this object. The way that profilers find memory leaks is to trace references to a leaked object.
What do I mean by "tracing"? A leaked object can be referenced by another object which itself is a leak. In turn, this object may also be a leak, and so on. This repeats until an object is found that references a leaked object by mistake. This reference is where the problem is, and what you need to fix. Let me try to clarify this by illustrating this with a picture from my previous blog:
In this picture the AppClassloader, LeakServlet.class, STATICNAME, CUSTOMLEVEL, LeakServlet$1.class are all leaked objects. Due to static objects (e.g. STATICNAME) in the picture, that may in turn reference other objects, the number of leaked objects may be in the thousands. Going over each leaked object manually to check if there are any incidental references to it (the red reference in the picture) until you find the troublesome object (CUSTOMLEVEL) is laborious. You would rather have a program find the violating reference for you.
A profiler doesn't tell you which leaked object is interesting to look at (CUSTOMLEVEL). Instead it gives you all leaked objects. Let's say that you would look at STATICNAME. The profiler now should find the route STATICNAME to LEAKSERVLET.class to AppClassloader to LeakServlet1$1.class, to CUSTOMLEVEL to Level.class. In this route, the red line in the picture is the reference that actually causes the leak. I said the profiler should find this route. However, all the profilers that we tried, stop tracing as soon as they reach a class object or classloader. There's a good reason for that: the number of traces grows enormous if it follows through the references through classes. And in most cases, these traces are not very useful.
So no luck with profilers! We need to try something else.
JDK 6.0 to the rescue
When Edward Chou and I worked on tracking down classloader leaks last year, we tried to run the JVM with HPROF and tried to trigger a memory dump; we looked at using Hat to interpret the dump. Hat stands for Heap Analysis Tool, and was developed to read dump files generated with HPROF. Unfortunately, the hat tool blew up reading our dump files. Because we didn't think it was difficult to parse the dump file, we wrote a utility to read the file and track the memory leak.
That was last year. This year we have JDK 6.0; this new JDK comes with a few tools that make looking at the VM's memory a lot simpler. First of all, there's a tool called jmap. This command line tool allows you to trigger a dump file without HPROF. It is as simple as typing something like:
jmap -dump:format=b,file=leak 3144
Here leak is the filename of the dump, and 3144 is the PID of the process. To find the PID, you can use jps.
Secondly, Hat is now part of the JDK. It is now called jhat. You can run it using a command line like:
jhat -J-Xmx512m leak
Here leak is the name of the dump file, and as you may have guessed, -J-Xmx512m is a parameter to specify how much memory jhat is allowed to allocate.
When you start jhat it reads the dump file and then listens on an HTTP port. You point your browser to that port (7000 by default) and through that you can browse the memory heap dump. It's a very convenient way of looking at what objects are in memory and how they are connected.
So, it seemed like a good idea to check out what can be done with these new tools to find classloader leaks.
... or not?
Unfortunately, jhat, just like the profilers we tried, also stops tracing when it encounters a class. Now what? I decided to download the JDK source code and find out what the problem is. Building the whole JDK is a difficult task from what I gather from the documentation. Fortunately, jhat is a nicely modularized program; I could just take the com.sun.tools.hat-packages out of the source tree, load them in my favorite editor and compile the code. The patched code was easily packaged and run: I just jar-ed it and added it to the lib/ext directory of the JDK:
jar -cf C:\apps\Java\jdk1.6.0\jre\lib\ext\ahat.jar -C hat\bin .
jhat leak
This was really as easy as pie. So after running the program in the debugger for some time, I figured out how it works and what changes I wanted to make. The change is that when you follow the references from a classloader, the modified jhat will follow through all traces from all the instances of the classes that it loaded. With that change, finding the cause of a classloader leak is simple.
An example
Let's look at the example from my previous blog as depicted in the picture above. Using NetBeans I created the following servlet and deployed it to Glassfish:
1 package com.stc.test;
2
3 import java.io.*;
4 import java.net.*;
5 import java.util.logging.Level;
6 import java.util.logging.Logger;
7 import javax.servlet.*;
8 import javax.servlet.http.*;
9
10 public class Leak extends HttpServlet {
11
12 protected void processRequest(HttpServletRequest request, HttpServletResponse response)
13 throws ServletException, IOException {
14 response.setContentType("text/html;charset=UTF-8");
15 PrintWriter out = response.getWriter();
16 out.println("<html><body><pre>");
17 Level custom = new Level("LEAK", 950) {};
18 Logger.getLogger(this.getClass().getName()).log(custom, "New level created");
19 out.println("</pre></body></html>");
20 out.close();
21 }
22+ HTTPServlet methods. Click on the + sign on the left to edit the code
48 }
49
I invoked the servlet to cause the leak. Next I undeployed the servlet. Then I triggered a heap dump:
jmap -dump:format=b,file=leak 3144
and fired up the modified jhat:
jhat -J-Xmx512m leak
and brought up the browser. The opening screen shows amongst other things, all classes that are found in the dump:
visit
http://localhost:7000/
Scrolling down, I see Reference Chains from Rootset / Exclude weak refs . Clicking on this link invokes the code that I modified; the following screen comes up:
And there's the link to java.util.Logging.Level that we've been looking for!
Easy as pie!
Summarizing, the steps are:
undeploy the application that is leaking
trigger a memory dump
run jhat (with modification)
find a leaked class
locate the classloader
find the "Reference chains from root set"
inspect the chains, locate the accidental reference, and fix the code
I'll contact the JDK team to see if they are willing to accept the changes I made to jhat. If you cannot wait, send me an email or leave a comment.
Update (April 2007): Java SE SDK 6.0 update 1 has the updated code.
Other Permgen space tidbits
After fixing the classloader leak, you of course want to test to see if the memory leak has disappeared. You could again trigger a memory dump and run jhat. What you also could try is to see if the amount of used permgen space memory goes up continuously after each deployment/undeployment of your application.
You can monitor permgen space usage using jconsole. You can see the memory usage go up when you repeatedly deploy and undeploy an application. However, this may not be a classloader / memory leak. As it turns out, it's difficult to predict when the garbage collector cleans up permgen space. Pressing the button in Run GC in jconsole does not do the trick. Only when you encounter a java.lang.OutOfMemoryError: PermGen space exception can you be sure that there really was no memory. This is a bit more involved than it should be!
How can we force the garbage collector to kick in? We can force a java.lang.OutOfMemoryError: PermGen space and then releasing the memory after which we force the garbage collector to kick in. I wrote the following servlet to do that:
package com.stc.test;
import java.io.*;
import java.util.ArrayList;
import javax.servlet.*;
import javax.servlet.http.*;
public class RunGC extends HttpServlet {
private static class XClassloader extends ClassLoader {
private byte[] data;
private int len;
public XClassloader(byte[] data, int len) {
super(RunGC.class.getClassLoader());
this.data = data;
this.len = len;
}
public Class findClass(String name) {
return defineClass(name, data, 0, len);
}
}
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<html><body><pre>");
try {
// Load class data
byte[] buf = new byte[1000000];
InputStream inp = this.getClass().getClassLoader()
.getResourceAsStream("com/stc/test/BigFatClass.class");
int n = inp.read(buf);
inp.close();
out.println(n + " bytes read of class data");
// Exhaust permgen
ArrayList keep = new ArrayList();
int nLoadedAtError = 0;
try {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
XClassloader loader = new XClassloader(buf, n);
Class c = loader.findClass("com.stc.test.BigFatClass");
keep.add(c);
}
} catch (Error e) {
nLoadedAtError = keep.size();
}
// Release memory
keep = null;
out.println("Error at " + nLoadedAtError);
// Load one more; this should trigger GC
XClassloader loader = new XClassloader(buf, n);
Class c = loader.findClass("com.stc.test.BigFatClass");
out.println("Loaded one more");
} catch (Exception e) {
e.printStackTrace(out);
}
out.println("</pre></body></html>");
out.close();
}
In this servlet a custom classloader is instantiated which loads a class in that classloader. That class is really present in the web classloader, but the custom classloader is tricked by not delegating to the parent classloader; instead the classloader is instantiating the class using the bytes of the class obtained through getResourceAsStream().
In the servlet it tries to allocate as many of these custom classes as possible, i.e. until the memory exception occurs. Next, the memory is made eligible for garbage collection, and one more classloader is allocated thereby forcing garbage collection.
The number of custom classes that can be loaded until a memory exception occurs, is a good measure of how much permgen space memory is available. As it turns out, this metric is a much more reliable than the one that you get from jconsole.
And more
Edward Chou is thinking of some other ideas to further automate the process of determining exactly where the cause of a classloader leak is. E.g. it should be possible to identifiy the erroneous reference (the red line in the picture) automatically, since this reference is from one classloader to another. Check his blog in the coming days.
Update (April 2007): You can find an interesting usage of jhat's Object Query Language on Sundarajan's blog to compute histograms of reference chains.
分享到:
相关推荐
Ruby Performance Optimization: Why Ruby is Slow, and How to Fix It By 作者: Alexander Dymo ISBN-10 书号: 1680500694 ISBN-13 书号: 9781680500691 Edition 版本: 1 出版日期: 2015-11-29 pages 页数: (202) ...
书名《如何不当编程:C++篇》("How Not to Program in C++.pdf")暗示了本书将展示一些不正确的编程实践,这与大多数教科书的正面教学方法形成鲜明对比。它旨在通过揭示错误的编程方式来教育读者,从而避免重蹈覆辙...
don't know how to prevent this. The codec compresses real movies nicely at frametimes , when changing the settings in its dialog box as follows: Bitrate (1st page): to >=1300 Max Keyframe interval ...
Appstart是一个跨平台的应用程序启动程序(用Java编写),它使您无需编写另一个shell /批处理脚本来启动Java桌面应用程序。 All you have to do is: put the appstart.jar in your application dir. You can rename...
In this comprehensive guide to Ruby optimization, you'll learn how to write faster Ruby code--but that's just the beginning. See exactly what makes Ruby and Rails code slow, and how to fix it. Alex ...
PuTTy ssh远程连接工具 ... Bug fix: removed a bad interaction between the 'clear scrollback' operation and mouse selection that could give rise to the dreaded "line==NULL" assertion box.
In a sense, this book is a long answer to those questions—how to use, where to fit, and how to get the most value out of mining and modeling in strategic and tactical business applications.
Of course, the program must adhere to the rules set by Windows, else it will cause the dreaded General Protection Fault. Each program is alone in its address space. This is in contrast to the ...
Tricks of the Windows video Game Programming <br>PART I Windows Programming Foundations 7 1 Journey into the Abyss 9 A Little History.............................................................
When you purchase Python: Programming Language for Beginners - Learn In A Day, you'll find out the importance of this language, and how to begin improving your skills! These fun and easy tips ...
* Fixed the dreaded "Clock skew detected" compiler warning! * Fixed many class browser bugs, including some that had to do with class folders. Version 4.9.5.4 * Under NT, 2000 and XP, user ...
then the (somewhat) dreaded scourge of debugging it. And then repeat until the program works as expected. It is possible to debug programs by inserting code that prints values of various interesting ...
浮华用于终端输出的 ANSI 彩色魅力颜色和效果可以按任何顺序链接和添加。 您可以指定一种前景色和... underlineputs "Beware the dreaded " + "Blink Attack!" . foreground ( :white ) . background ( :magenta ) . bl
例如,attach the file to the email(把文件附加到邮件中)。在这里,我们可以看到 attach 的相关性,都是指固定或附加。 emotion 是一个名词,意思是“强烈的感情”或“情感”。例如,the emotion of happiness...
void DESearch(Groph *p, Groph *&q, int &path, bool Dreaded[]) { if (Dreaded[p->data] == false) { cout << p->data + 1 ; Dreaded[p->data] = true; path++; } static bool contin = true; while ...
The Dreaded Cartesian Product 488 Failure to Enforce File System Structure Conventions 489 Allowing Large Tables to Take Default Storage Parameters489 Placing Objects in the System Tablespace490 ...
"Integer-Factorization-Dreaded-List-of-Primes.pdf"很可能包含了一个预先计算好的素数列表,供算法使用。而"TrialDivLP_20210206.zip"可能是包含了 Trial Division 算法的具体实现,或者是一些实验数据和结果。 ...