Affects: Xerces-J 2.9.1
** Summary **
Once a Node had attached a user data object, that Node object will never
be eligible for garbage collection (even if removed from the tree)
unless its parent Document is garbage collected. This happens even if
the user data is removed from that Node as described in the documentation.
** Description of cause **
User data is stored on the Document object for all Nodes, not on the
individual Nodes themselves. When user data is first attached to a Node,
a new entry in the Hashtable is created with the Node object itself as
key, and a secondary Hashtable as value, into which the user data KV-
pairs are stored in.
When user data is deleted, the respective KV pairs are removed from the
secondary Hashtable as expected. However, even when the secondary
Hashtable for a Node gets empty, its top-level Hashtable entry in the
Document is not removed alongside. Since that entry's key still refers
to the node, the latter cannot get garbage collected.
The following pseudo code will leak Node n until the Document doc is
discarded:
{
Node n = doc.createElement( "a" );
n.setUserData( "key", "value", null );
n.setUserData( "key", null, null );
}
** Possible remedies **
I can see two ways of solving the issue:
1) Do not use the node itself as the key in the primary Hashtable.
2) Discard a Node's entry in the primary Hashtable as soon as its
secondary (key-) Hashtable gets empty.
In my tests during tracking down this issue, I settled for solution 2).
** Suggested patch **
I'm therefore suggesting the following patch to
org.apache.xerces.dom.CoreDocumentImpl.java, setUserData() (#2297):
After removing a key from the Node's secondary Hashtable t, we check if
it is now empty. If it is, no user data is attached to that Node any
longer, and we remove its entry in the primary Hashtable 'userData',
thereby removing the incoming reference to it for it to be eligible for
garbage collection.
Here's the complete function code with my suggested patch applied,
bracketed by
// -> added CR
...
// <-
--snip--
public Object setUserData(Node n, String key,
Object data, UserDataHandler handler) {
if (data == null) {
if (userData != null) {
Hashtable t = (Hashtable) userData.get(n);
if (t != null) {
Object o = t.remove(key);
// -> added CR
if( t.size() == 0 )
userData.remove( n );
// <-
if (o != null) {
UserDataRecord r = (UserDataRecord) o;
return r.fData;
}
}
}
return null;
}
else {
Hashtable t;
if (userData == null) {
userData = new Hashtable();
t = new Hashtable();
userData.put(n, t);
}
else {
t = (Hashtable) userData.get(n);
if (t == null) {
t = new Hashtable();
userData.put(n, t);
}
}
Object o = t.put(key, new UserDataRecord(data, handler));
if (o != null) {
UserDataRecord r = (UserDataRecord) o;
return r.fData;
}
return null;
}
}
--snip--
Kind regards
Christian Roth
--
Christian Roth (CTO) * Phone: +49 (0)89 89 04 32 95
infinity-loop GmbH * Neideckstr. 25 * 81249 München * Germany
HRB 136 783 (AG München) * Geschäftsführer: Dr. Stefan Hermann
Web: http://www.infinity-loop.de
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]