See which of your colleagues or former colleagues are already on Java Link: Check out the Contact Finder
News »Browse Articles » ClassLoaderLocal: How to avoid ClassLoader leaks on application redeploy
0
Vote Vote

ClassLoaderLocal: How to avoid ClassLoader leaks on application redeploy

Views 5 Views    Comments 0 Comments    Share Share    Posted 22-06-2009  
"OutOfMemoryError: PermGen" is a very common message to see after a few redeploys. The reason why it`s so common is that it`s amazingly easy to leak a class loader. It`s enough to hold a single outside reference to an object instantiated from a class loaded by the said class loader to prevent that class loader from being GC-d.

In this post I`ll review how we solved this problem in JavaRebel, and share the solution with you. It`s not a magical solution, but it will help alleviate some of the problems introduced in both libraries and applications in Java EE.

The most common way to leak is to register some kind of a callback object and never deregister it. E.g. look at the following code:
view source
print?
1.Core.addListener(new MyListener());
If Core is a part of the framework/platform/container then it will hold to MyListener long after the application was redeployed and the class loader left hanging.



Let`s see if we can do anything to solve this. The Core implementation looks something like this:
view source
print?
01.public class Core {
02. List listeners = new ArrayList();
03.
04. void addListener(Listener l) {
05. listeners.add(l);
06. }
07.
08. void fireListeners() {
09. // Exercise for the reader!
10. }
11.}

The problem is that listeners provides a strong reference to the Listener object. What if we replace it by a weak one?
view source
print?
01.public class Core {
02. List listeners = new ArrayList();
03.
04. void addListener(Listener l) {
05. listeners.add(new WeakReference(l));
06. }
07.
08. void fireListeners() {
09. // Exercise for the reader!
10. }
11.}

Unfortunately although this does solve the problem of GC-ing the class loader, it doesn`t really work. The Listener behind the weak reference will be GC-d at first opportunity and after that it`ll no longer receive any callbacks. To illustrate why it`s a problem the code above is basically equivalent to throwing the reference away altogether:
view source
print?
1.public class Core {
2. List listeners = new ArrayList();
3.
4. void addListener(Listener l) {
5. // Listener is ignored and GC-d
6. }
7.}

Replacing weak reference with a soft one doesn`t improve the situation, just delays the inevitable a bit further. Both are useful for caches, where objects can be recreated at will, but not in this case where we have an externally created object.

So what do we do? What we`d like to do is have the Listener reference to depend on the class loader somehow. Unfortunately, to the best of my knowledge, there isn`t a ready-made solution for that, and there`s no way to achieve it with any combinations of weak references without causing problems.

What we`d like to have is an ability to add a strong reference to the class loader: in other words have it carry a custom property:
view source
print?
01.void addListener(Listener l) {
02. ClassLoader cl = l.getClass().getClassLoader();
03. List lls = (List) cl.getProperty("CoreListeners");
04. if (lls == null) {
05. lls = new ArrayList();
06. cl.putProperty("CoreListeners", lls);
07. }
08. lls.add(l);
09.}

That would work, wouldn`t it? Well, not quite. We also need to save a reference to the class loaders, so that we could later go over all of them. Here the WeakHashMap is useful:
view source
print?
1.Map classLoaders = new WeakHashMap();
2.
3.void addListener(Listener l) {
4. //...
5. classLoaders.put(cl, Boolean.TRUE);
6.}

There`s not WeakHashSet in Java, so we`re just using a boolean flag as the value.

So this would probably work, but unfortunately class loaders don`t have a getProperty()/putProperty() API. However, it turns out that with a bit of a hack we can simulate it, by generating a unique class per class loader to hold the properties for us. Let`s see how it`s done!

We start with a little boilerplate:
view source
print?
01.class ClassLoaderLocalMap {
02. private static Method defineMethod;
03. private static Method findLoadedClass;
04.
05. static {
06. try {
07. defineMethod = ClassLoader.class.getDeclaredMethod(
08. "defineClass",
09. new Class[] {
10. String.class,
11. byte[].class,
12. int.class,
13. int.class });
14. defineMethod.setAccessible(true);
15.
16. findLoadedClass =
17. ClassLoader.class.getDeclaredMethod(
18. "findLoadedClass",
19. new Class[] { String.class});
20. findLoadedClass.setAccessible(true);
21. }
22. catch (NoSuchMethodException e) {
23. throw new RuntimeException(e);
24. }
25. }
26.}

This will give us access to ClassLoader protected methods defineClass() and findLoadedClass() later on. Now let`s setup the basic API:
view source
print?
01.public static void put(
02. ClassLoader cl,
03. Object key,
04. Object value) {
05. // Synchronizing over ClassLoader is safest
06. synchronized (cl) {
07. getLocalMap(cl).put(key, value);
08. }
09.}
10.
11.public static Object get(
........

Source:
http://java.dzone.com/articles/classloaderlocal-how-avoid
0
Vote  Vote
Enter your comment:
No Comments For This News

Search News

What's the News?

Post a link to something interesting from another site, or submit your own original writing for the Java community to read.

Most Popular News

Most Recent User Submitted News