问题
In an attempt to solve this problem, I built a (very) small project that is reproducing part of it. It is a NetBeans project using Glassfish v2.1.1 and OpenJpa-1.2.2.
Globally, the goal is to be able to reload dynamically some business code (called 'tasks') without the need to (re)make a full deployment (eg via asadmin). In the project there are two of them: PersonTask and AddressTask which are simply accessing some data and printing them out.
In order to do that, I've implemented a custom class loader that read the binary of class files and inject it via the defineClass
method. Basically, this CustomClassLoader is a singleton and is implemented like this:
public class CustomClassLoader extends ClassLoader {
private static CustomClassLoader instance;
private static int staticId = 0;
private int id; //for debugging in VisualVM
private long threadId; //for debugging in VisualVM
private CustomClassLoader(ClassLoader parent) {
super(parent);
threadId = Thread.currentThread().getId();
id = staticId;
++staticId;
}
private static CustomClassLoader getNewInstance() {
if (instance!=null) {
CustomClassLoader ccl = instance;
instance = null;
PCRegistry.deRegister(ccl); //https://issues.apache.org/jira/browse/GERONIMO-3326
ResourceBundle.clearCache(ccl); //found some references in there while using Eclipse Memory Analyzer Tool
Introspector.flushCaches(); //http://java.jiderhamn.se/category/classloader-leaks/
System.runFinalization();
System.gc();
}
ClassLoader parent = Thread.currentThread().getContextClassLoader();
instance = new CustomClassLoader(parent);
return instance;
}
//...
}
//this class is included in the EAR like a normal class
public abstract class AbstractTask {
protected Database database; /* wrapper around the EntityManager, filled when instance is created */
public abstract void process(Integer id);
}
//this one is dynamically loaded by the CustomClassLoader
public class PersonTask extends AbstractTask {
@Override
public void process(Integer id) {
//keep it empty for now
}
}
In my EJB facade (EntryPointBean), I simply do a lookup of the class, create a new instance of it and call the process
method on it. The code in the project is slightly different, but the idea is quite the same:
CustomClassLoader loader = CustomClassLoader.getNewInstance();
Class<?> clazz = loader.loadClass("ch.leak.tasks.PersonTask");
Object instance = clazz.newInstance();
AbstractTask task = (AbstractTask)instance;
/* inject a new Database instance into the task */
task.process(...);
Until now, all is fine. If this code is run many times (via ch.leak.test.Test
), there will be only one single instance of the CustomClassLoader when a heap analysis is done, meaning the previous instances have been successfully collected.
Now, here is the line triggering a leak:
public class PersonTask extends AbstractTask {
@Override
public void process(Integer id) {
Person p = database.getEntity("SELECT p FROM Person p WHERE p.personpk.idpk=?1", new Long(id));
//...
}
}
This simple access to the database has a strange consequence: the first time the code is run, the CustomClassLoader being used will never be garbage collected (even without any GC roots). However, all the further CustomClassLoader created won't leak.
As we can see in the dump below (done with VisualVM), the CustomClassLoader with instance id 0 is never garbage collected...
Finally, one other thing I've seen when exploring the heap dump: my entities are declared twice in the PermGen and half of them have no instances and also no GC root (but they are not linked to the CustomClassLoader).
It seems that OpenJPA has something to do with those leaks... but I don't know where I can search for more information of what I'm doing wrong. I have also put the heap dump directly in the zip with the project. Does anyone have an idea ?
Thanks !
来源:https://stackoverflow.com/questions/14113554/custom-classloader-not-garbage-collected