We have a complex application running under Glassfish V2.1.1. In order to be able to load our code dynamically, we have implemented a CustomClassloader which is able to redefine classes. The behaviour is quite easy: when a dynamically loaded class has change, the current instance of the CustomClassloader is "dropped" and a new one is create to redefine the needed classes.
This works well except that after a few number of time the same class has been reloaded (hence each time a new CustomClassloader is created), we get a PermGen space error because the other instances of CustomClassloader are not garbage collected. (There should be only one single instance of this class)
I tried different methods to track down where the leak is:
- visualvm => I make a heap dump and extract all the instances of the CustomClassloader. I can see that none of them have been finalized. When I check for the nearest GC root, visualvm tells me that there is none (except for the last instance, because it is the 'real' used one).
- jmap/jhat => It gives me almost the same result: I see all the instances of CustomClassloader and then when I click on the link to see where are the references on one of them, I get a blank page meaning there is none...
- Eclipse Memory Analyzer Tool => I get a strange result when I run the following OQL query:
SELECT c FROM INSTANCEOF my.package.CustomClassloader c
There is only one result, indicating there is only one single instance which is obviously not correct.
I also checked this link and implemented some resources release when a new CustomClassloader is created, but nothing changes: the PermGen memory is still increasing.
So I'm probably missing something and the difference between points (1-2) and (3) shows something I do not understand. Where can I look to get an idea of what's wrong ? Since all the tutorials I followed show how to search the leaking references by using the "Search nearest GC root" feature (and in my case there is none), I don't know how I can track the error.
EDIT 1: I uploaded an example of the heap dump here. The ClassLoader not being unloaded can be selected in visualvm with the following query: select s from saierp.core.framework.system.SAITaskClassLoader s
One can see that there are 4 instances and the three first should have been collected because there is no GC root... There must be a reference somewhere but I don't know how I can search for it. Any hint is welcomed :)
EDIT 2: After some deeper tests I see a very strange pattern. The leak seems to depend on the data that are being loaded by OpenJPA: if no new data is loaded, then the classloader can be GCed, otherwise it is not. Here is the code I use when a create a new SAITaskClassLoader to 'clear' the old one:
PCRegistry.deRegister(cl);
LogFactory.release(cl);
ResourceBundle.clearCache(cl);
Introspector.flushCaches();
= Pattern 1 (Classloader is GCed): =
- New SAITaskClassLoader
- Load Data D1, D2, ..., Dn
- New SAITaskClassLoader
- Load Data D1, D2, ..., Dn
- ...
= Pattern 2 (Classloader is NOT GCed): =
- New SAITaskClassLoader
- Load Data D1, D2, D3
- New SAITaskClassLoader
- Load Data D3, D4, D5
- New SAITaskClassLoader
- Load Data D5, D6, D7
- ...
In all cases, the SAITaskClassLoader that have been cleared have no GC root. We are using OpenJPA 1.2.1.
Thanks & Best regards