JDK-6616095 : AWT's WindowDisposerRecord keeps AppContext alive too long
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.awt
  • Affected Version: 7
  • Priority: P2
  • Status: Closed
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2007-10-12
  • Updated: 2011-05-18
  • Resolved: 2011-05-18
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 6 JDK 7
6u10Fixed 7 b25Fixed
Related Reports
Relates :  
Description
The Java 2D Disposer is keeping Java objects alive too long. This
mechanism was introduced into Java 2D to avoid the use of finalizers,
which require two full garbage collections to reclaim all of the
associated storage. Due to the structure of the Disposer, it still
requires two full GCs to reclaim storage associated with dead
AppContexts.

The following two graph traces come from the new Java Plug-In (not yet
integrated into 6u5) and are meant to illustrate what is keeping
objects alive unnecessarily. An applet (MemoryLeakTest) was loaded
which allocated a significant amount of memory, and then we switched
away from the web page to another applet. The heap dump was taken at
this point. At this stage, the AppContext for the MemoryLeakTest
applet has been disposed and all plugin-related resources cleaned up.
There should not be any strong references to the applet left at this
point. However, it is clear that the AppContext, and ultimately the
applet, are still reachable through the value field of the associated
Hashtable entry in the Disposer's global table mapping references to
DisposerRecords. Attempting to reload the MemoryLeakTest provokes an
OutOfMemoryError because the JVM only performs one full GC to try to
satisfy the allocation done by the second MemoryLeakTest instance.

(The reason that this issue does not happen with the old Java Plug-In
is that it eagerly nulls out the reference to the applet, which is a
hack that should not be necessary. We are going to introduce a similar
hack into the new Java Plug-In to work around this problem under
6613370.)

Static reference from sun.java2d.Disposer.records (from class sun.java2d.Disposer) :
--> java.util.Hashtable@0x2e6f8e8 (40 bytes) (field table:)
--> [Ljava.util.Hashtable$Entry;@0x2fdfbd8 (196 bytes) (Element 39 of [Ljava.util.Hashtable$Entry;@0x2fdfbd8:)
--> java.util.Hashtable$Entry@0x2fec8c8 (24 bytes) (field next:)
--> java.util.Hashtable$Entry@0x2fee6a0 (24 bytes) (field value:)
--> java.awt.Window$WindowDisposerRecord@0x2fef850 (20 bytes) (field context:)
--> sun.awt.AppContext@0x2fdf9b8 (49 bytes) (field contextClassLoader:)
--> sun.plugin2.applet.Applet2ClassLoader@0x2fdc358 (98 bytes) (field manager:)
--> sun.plugin2.applet.Applet2Manager@0x2fdc2e8 (105 bytes) (field applet:)
--> MemoryLeakTest@0x2febfb0 (260 bytes) (field largeArray:)
--> [B@0x2ff5fd8 (41943048 bytes) 

Static reference from sun.java2d.Disposer.records (from class sun.java2d.Disposer) :
--> java.util.Hashtable@0x2e6f8e8 (40 bytes) (field table:)
--> [Ljava.util.Hashtable$Entry;@0x2fde8b8 (196 bytes) (Element 39 of [Ljava.util.Hashtable$Entry;@0x2fde8b8:)
--> java.util.Hashtable$Entry@0x2febba0 (24 bytes) (field next:)
--> java.util.Hashtable$Entry@0x2feda30 (24 bytes) (field value:)
--> java.awt.Window$WindowDisposerRecord@0x2fef230 (20 bytes) (field context:)
--> sun.awt.AppContext@0x2febc20 (49 bytes) (field contextClassLoader:)
--> sun.plugin2.applet.Applet2ClassLoader@0x2fdb5c8 (98 bytes) (field manager:)
--> sun.plugin2.applet.Applet2Manager@0x2fdb680 (105 bytes) (field listeners:)
--> java.util.ArrayList@0x2fdf048 (20 bytes) (field elementData:)
--> [Ljava.lang.Object;@0x2fe6418 (48 bytes) (Element 0 of [Ljava.lang.Object;@0x2fe6418:)
--> sun.plugin2.main.client.PluginMain$1$2@0x2febb60 (16 bytes) (field val$helper:)
--> sun.plugin2.main.client.DragHelper@0x2fed970 (45 bytes) (field component:)
--> MemoryLeakTest@0x2ff4050 (260 bytes) (field largeArray:)
--> [B@0x2ff5fe8 (41943048 bytes)

Conceptually the DisposerRecord should record the minimal amount of
information needed in order to clean up the associated resource. It
should not need a reference to the AppContext. At this point in the
code, the AppContext has been disposed and is effectively dead.

The steps in reclaiming the disposer records are that the weak or
phantom reference pointing to the object in question is cleared during
a full GC; the reference is enqueued; the Disposer polls the queue,
doing the cleanup work and removing the record from the "records"
table; and then another full GC occurs, removing the disposer record
and allowing the value and the object graph referenced from it to be
GCd.

Again, the disposer record should point to the minimal amount of
information needed to clean up any native resources associated with
the given object. It should clearly not point to the AppContext
because doing so is meaningless at least in situations like the above
where the AppContext has already been disposed.

This issue may also be causing native resources to be held on to
longer than they should be, depending on the object graphs referenced
by the disposer records.
After discussion with ###@###.### and ###@###.###, it seems clear that the problem lies specifically in the java.awt.Window$WindowDisposerRecord class and not in the Java 2D Disposer generally, which does not know about AppContexts. Jim states:

> ...their private implementation of a disposer already knows enough to hold onto
> weak references to the window itself - they just need to extend that philosophy
> one further to holding on to a weak reference to the AppContext as well.
> Currently they hold onto a non-weak reference to it.  If the context was
> reclaimed and the weak reference is empty then they don't need to remove
> the window from the list. 

and Dmitri points out this forum thread for more information on writing Disposer records:

  http://forums.java.net/jive/thread.jspa?threadID=31821&tstart=0

Reassigning to AWT.

Comments
EVALUATION WindowDisposerRecord needs to reference to app context using weak reference
15-10-2007

SUGGESTED FIX ------- Window.java ------- *** /tmp/sccs.mZ5f7F 2007-10-16 01:52:06.000000000 +0400 --- Window.java 2007-10-16 01:33:48.000000000 +0400 *************** *** 356,376 **** static class WindowDisposerRecord implements sun.java2d.DisposerRecord { final WeakReference<Window> owner; final WeakReference weakThis; ! final AppContext context; WindowDisposerRecord(AppContext context, Window victim) { owner = new WeakReference<Window>(victim.getOwner()); weakThis = victim.weakThis; ! this.context = context; } public void dispose() { Window parent = owner.get(); if (parent != null) { parent.removeOwnedWindow(weakThis); } ! Window.removeFromWindowList(context, weakThis); } } ! private void init(GraphicsConfiguration gc) { GraphicsEnvironment.checkHeadless(); --- 356,379 ---- static class WindowDisposerRecord implements sun.java2d.DisposerRecord { final WeakReference<Window> owner; final WeakReference weakThis; ! final WeakReference<AppContext> context; WindowDisposerRecord(AppContext context, Window victim) { owner = new WeakReference<Window>(victim.getOwner()); weakThis = victim.weakThis; ! this.context = new WeakReference<AppContext>(context); } public void dispose() { Window parent = owner.get(); if (parent != null) { parent.removeOwnedWindow(weakThis); } ! AppContext appContext = context.get(); ! if (appContext != null) { ! Window.removeFromWindowList(appContext, weakThis); ! } } } ! private void init(GraphicsConfiguration gc) { GraphicsEnvironment.checkHeadless();
15-10-2007