org.datanucleus.FetchGroup#planListeners holds strong references of org.datanucleus.FetchPlan, unfortunately, FetchGroup#deregisterListener is normally never invoked in RunTime. Since FetchPlan holds strong reference of org.datanucleus.ObjectManagerImpl which holds strong reference of org.datanucleus.jdo.JDOPersistenceManager, FetchGroup#planListeners leaks memory quickly soon Out Of Memory.
The Test Case to be attached, shows both FetchPlan & PersistenceManager still referenced after GC.
Just for the only purpose of Proof of Concept, following refinements seem solved the problem:
3-1. Add this into JDOPersistenceManager#close()
3-2. Add this into org.datanucleus.jdo.JDOQuery#closeAll()
if (fetchPlan != null)
3-3. Add this into org.datanucleus.store.rdbms.query.legacy.JDOQLQuery
public void closeAll()
if (candidates instanceof CollectionCandidates)
else if (candidates instanceof Extent)
Now the Test Case shows both FetchPlan & PersistenceManager GCed!
However, neither is user obligated to close()/clossAll(), nor should a JDO implementation count on so. Therefore, I personally think the real fix should be weakly referencing FetchGroup#planListeners elements, such as WeakHashMap#keySet().
After the real fix, both "pm.close();" & "query.closeAll();" should be commented out of the Test Case to verify the fix.
Given the weakly referencing fix, I personally still recommend above code change as they help GC.