changeset 8001:65b4034a7da1

Fix RT-36205: Resource warnings while printing Reviewed by: kcr, prr
author flar <James.Graham@oracle.com>
date Mon, 08 Sep 2014 15:58:17 -0700
parents 3abef7f2f13f
children d7ac4b8e6371
files modules/graphics/src/main/java/com/sun/javafx/tk/quantum/PresentingPainter.java modules/graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java modules/graphics/src/main/java/com/sun/javafx/tk/quantum/UploadingPainter.java modules/graphics/src/main/java/com/sun/prism/impl/BaseResourcePool.java modules/graphics/src/main/java/com/sun/prism/impl/ManagedResource.java modules/graphics/src/main/java/com/sun/prism/impl/ResourcePool.java modules/graphics/src/main/java/com/sun/prism/j2d/print/J2DPrinterJob.java
diffstat 7 files changed, 379 insertions(+), 354 deletions(-) [+]
line wrap: on
line diff
--- a/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/PresentingPainter.java	Mon Sep 08 15:47:52 2014 -0700
+++ b/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/PresentingPainter.java	Mon Sep 08 15:58:17 2014 -0700
@@ -30,7 +30,6 @@
 import com.sun.prism.Graphics;
 import com.sun.prism.GraphicsPipeline;
 import com.sun.prism.impl.Disposer;
-import com.sun.prism.impl.ManagedResource;
 
 /**
  * The PresentingPainter is used when we are rendering to the main screen.
@@ -123,7 +122,7 @@
             ViewScene viewScene = (ViewScene)sceneState.getScene();
             viewScene.setPainting(false);
 
-            ManagedResource.freeDisposalRequestedAndCheckResources(errored);
+            factory.getTextureResourcePool().freeDisposalRequestedAndCheckResources(errored);
 
             renderLock.unlock();
         }
--- a/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java	Mon Sep 08 15:47:52 2014 -0700
+++ b/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java	Mon Sep 08 15:58:17 2014 -0700
@@ -1432,8 +1432,8 @@
                     errored = true;
                     t.printStackTrace(System.err);
                 } finally {
-                    Disposer.cleanUp();            
-                    ManagedResource.freeDisposalRequestedAndCheckResources(errored);
+                    Disposer.cleanUp();
+                    rf.getTextureResourcePool().freeDisposalRequestedAndCheckResources(errored);
                 }
             }
         });
--- a/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/UploadingPainter.java	Mon Sep 08 15:47:52 2014 -0700
+++ b/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/UploadingPainter.java	Mon Sep 08 15:58:17 2014 -0700
@@ -35,7 +35,6 @@
 import com.sun.prism.Texture.WrapMode;
 import com.sun.prism.impl.BufferUtil;
 import com.sun.prism.impl.Disposer;
-import com.sun.prism.impl.ManagedResource;
 
 /**
  * UploadingPainter is used when we need to render into an offscreen buffer.
@@ -184,7 +183,7 @@
 
             sceneState.getScene().setPainting(false);
 
-            ManagedResource.freeDisposalRequestedAndCheckResources(errored);
+            factory.getTextureResourcePool().freeDisposalRequestedAndCheckResources(errored);
 
             renderLock.unlock();
         }
--- a/modules/graphics/src/main/java/com/sun/prism/impl/BaseResourcePool.java	Mon Sep 08 15:47:52 2014 -0700
+++ b/modules/graphics/src/main/java/com/sun/prism/impl/BaseResourcePool.java	Mon Sep 08 15:58:17 2014 -0700
@@ -25,16 +25,54 @@
 
 package com.sun.prism.impl;
 
+import java.lang.ref.WeakReference;
+
 /**
  * The base implementation of the {@link ResourcePool} interface, providing
  * bookkeeping for the {@link managed()} method and support for sharing
  * resources amongst multiple pools.
+ * @param <T> the type of objects stored in this resource pool
  */
 public abstract class BaseResourcePool<T> implements ResourcePool<T> {
+    // Number of calls to checkAndDispose() before we consider a resource to
+    // have not been used in a hypothetical "FOREVER".
+    private static final int FOREVER = 1024;
+
+    /* 
+     * Which resources to clean up in a call to {@link cleanup}.
+     */
+    public static enum PruningLevel {
+        /**
+         * Only resources that have been reclaimed through GC or other
+         * asynchronous disposal mechanisms should be pruned.
+         * This basically makes sure that our resource accounting is up
+         * to date.
+         */
+        OBSOLETE,
+
+        /**
+         * Only (unlocked, nonpermanent) resources that have not been used in
+         * a very long time should be pruned.
+         */
+        UNUSED_FOREVER,
+
+        /**
+         * Only (unlocked, nonpermanent) resources that hold no interesting
+         * data should be pruned.
+         */
+        UNINTERESTING,
+
+        /**
+         * Any resource that is unlocked and nonpermanent should be pruned.
+         */
+        ALL_UNLOCKED
+    }
+
     long managedSize;
     final long maxSize;
-    final ResourcePool sharedParent;
+    final ResourcePool<T> sharedParent;
     private final Thread managerThread;
+    private WeakLinkedList<T> resourceHead;
 
     protected BaseResourcePool() {
         this(null, Long.MAX_VALUE);
@@ -44,11 +82,12 @@
         this(null, max);
     }
 
-    protected BaseResourcePool(ResourcePool parent) {
+    protected BaseResourcePool(ResourcePool<T> parent) {
         this(parent, parent.max());
     }
 
-    protected BaseResourcePool(ResourcePool parent, long max) {
+    protected BaseResourcePool(ResourcePool<T> parent, long max) {
+        this.resourceHead = new WeakLinkedList<>();
         this.sharedParent = parent;
         this.maxSize = ((parent == null)
                         ? max
@@ -56,6 +95,229 @@
         managerThread = Thread.currentThread();
     }
 
+    /**
+     * Clean up the resources in the indicated pool using a standard
+     * algorithm until at least the specified amount of resource units
+     * have been reclaimed.
+     * The standard algorithm uses the following stages until it obtains
+     * enough room in the pool:
+     * <ol>
+     * <li> Prune any resources which are already free, but have not been
+     *      accounted for yet.
+     * <li> Prune any resources that have not been used in a very long time.
+     * <li> Go through 2 more passes cleaning out any resources that have
+     *      not been used in a long time with decreasing cutoff limits for
+     *      the maximum age of the resource.
+     * <li> Finally, prune any resources that are not currently being used
+     *      (i.e. locked or permanent).
+     * </ol>
+     * 
+     * @param needed
+     * @return boolean indicating if the requested space is now available
+     */
+    public boolean cleanup(long needed) {
+        if (used() + needed <= target()) return true;
+        cleanup(PruningLevel.OBSOLETE, FOREVER);
+        if (used() + needed <= target()) return true;
+        cleanup(PruningLevel.UNUSED_FOREVER, FOREVER);
+        if (used() + needed <= target()) return true;
+        cleanup(PruningLevel.UNUSED_FOREVER, FOREVER/2);
+        if (used() + needed <= target()) return true;
+        cleanup(PruningLevel.UNUSED_FOREVER, FOREVER/4);
+        if (used() + needed <= target()) return true;
+        cleanup(PruningLevel.UNINTERESTING, FOREVER);
+        if (used() + needed <= target()) return true;
+        System.gc();
+        cleanup(PruningLevel.ALL_UNLOCKED, FOREVER);
+        if (used() + needed <= max()) return true;
+        // Our alternative is to return false and cause an allocation
+        // failure which is usually bad news for any SG, so it is worth
+        // sleeping to give the GC some time to find a dead resource that
+        // was dropped on the floor...
+        System.gc();
+        try { Thread.sleep(20); } catch (InterruptedException e) { }
+        cleanup(PruningLevel.ALL_UNLOCKED, FOREVER);
+        return (used() + needed <= max());
+    }
+
+    private void cleanup(PruningLevel plevel, int max_age) {
+        if (PrismSettings.poolDebug) {
+            switch (plevel) {
+                case OBSOLETE: System.err.print("Pruning"); break;
+                case UNUSED_FOREVER: System.err.print("Cleaning up unused in "+max_age); break;
+                case UNINTERESTING: System.err.print("Cleaning up uninteresting"); break;
+                case ALL_UNLOCKED: System.err.print("Aggressively cleaning up"); break;
+                default: throw new InternalError("Unrecognized pruning level: "+plevel);
+            }
+            System.err.println(" pool: "+this);
+            printSummary(false);
+        }
+        long wasused = used();
+        WeakLinkedList<T> prev = resourceHead;
+        WeakLinkedList<T> cur = prev.next;
+        while (cur != null) {
+            ManagedResource<?> mr = cur.getResource();
+            if (ManagedResource._isgone(mr)) {
+                if (PrismSettings.poolDebug) {
+                    System.err.println("pruning: "+mr+" ("+cur.size+")"+
+                                       ((mr == null) ? "" :
+                                        ((mr.isPermanent() ? " perm" : "") +
+                                         (mr.isLocked() ? " lock" : "") +
+                                         (mr.isInteresting() ? " int" : ""))));
+                }
+                recordFree(cur.size);
+                cur = cur.next;
+                prev.next = cur;
+            } else if (plevel != PruningLevel.OBSOLETE &&
+                       !mr.isPermanent() &&
+                       !mr.isLocked() &&
+                       (plevel == PruningLevel.ALL_UNLOCKED ||
+                        (plevel == PruningLevel.UNINTERESTING && !mr.isInteresting()) ||
+                        (/* plevel == PruningLevel.UNUSED_FOREVER && */ mr.getAge() >= max_age)))
+            {
+                if (PrismSettings.poolDebug) {
+                    System.err.println("disposing: "+mr+" ("+cur.size+") age="+mr.getAge()+
+                                       (mr.isPermanent() ? " perm" : "") +
+                                       (mr.isLocked() ? " lock" : "") +
+                                       (mr.isInteresting() ? " int" : ""));
+                }
+                mr.free();
+                mr.resource = null;
+                recordFree(cur.size);
+                cur = cur.next;
+                prev.next = cur;
+            } else {
+                prev = cur;
+                cur = cur.next;
+            }
+        }
+        if (PrismSettings.poolDebug) {
+            long isused = used();
+            System.err.println("cleaned up "+(wasused - isused)+" from pool: "+this);
+            printSummary(false);
+        }
+    }
+
+    /**
+     * Check that all resources are in the correct state for an idle condition
+     * and free any resources which were disposed from a non-resource thread.
+     * This method must be called on a thread that is appropriate for disposing
+     * and managing resources for the resource pools.
+     * The boolean {@code forgiveStaleLocks} parameter is used to indicate that
+     * an exceptional condition occurred which caused the caller to abort a
+     * cycle of resource usage, potentially with outstanding resource locks.
+     * This method will unlock all non-permanent resources that have outstanding
+     * locks if {@code forgiveStaleLocks} is {@code true}, or it will print out
+     * a warning and a resource summary if that parameter is {@code false}.
+     * 
+     * @param forgiveStaleLocks {@code true} if the caller wishes to forgive
+     *         and unlock all outstanding locks on non-permanent resources
+     */
+    @Override
+    public void freeDisposalRequestedAndCheckResources(boolean forgiveStaleLocks) {
+        boolean anyLockedResources = false;
+        WeakLinkedList<T> prev = resourceHead;
+        WeakLinkedList<T> cur = prev.next;
+        while (cur != null) {
+            ManagedResource<?> mr = cur.getResource();
+            if (ManagedResource._isgone(mr)) {
+                recordFree(cur.size);
+                cur = cur.next;
+                prev.next = cur;
+            } else {
+                if (!mr.isPermanent()) {
+                    if (mr.isLocked() && !mr.wasMismatched()) {
+                        if (forgiveStaleLocks) {
+                            mr.unlockall();
+                        } else {
+                            mr.setMismatched();
+                            anyLockedResources = true;
+                        }
+                    }
+                    mr.bumpAge(FOREVER);
+                }
+                prev = cur;
+                cur = cur.next;
+            }
+        }
+
+        if (PrismSettings.poolStats || anyLockedResources) {
+            if (anyLockedResources) {
+                System.err.println("Outstanding resource locks detected:");
+            }
+            printSummary(true);
+        }
+    }
+
+    public void printSummary(boolean printlocksources) {
+        int numgone = 0;
+        int numlocked = 0;
+        int numpermanent = 0;
+        int numinteresting = 0;
+        int nummismatched = 0;
+        int numancient = 0;
+        long total_age = 0;
+        int total = 0;
+        boolean trackLockSources = ManagedResource.trackLockSources;
+
+        double percentUsed = used() * 100.0 / max();
+        double percentManaged = managed() * 100.0 / max();
+        String str =
+            String.format("%s: %,d used (%.1f%%), %,d managed (%.1f%%), %,d total",
+                          this, used(), percentUsed,
+                          managed(), percentManaged,
+                          max());
+        System.err.println(str);
+
+        for (WeakLinkedList<T> cur = resourceHead.next; cur != null; cur = cur.next) {
+            ManagedResource<T> mr = cur.getResource();
+            total++;
+            if (mr == null || !mr.isValid() || mr.isDisposalRequested()) {
+                numgone++;
+            } else {
+                int a = mr.getAge();
+                total_age += a;
+                if (a >= FOREVER) {
+                    numancient++;
+                }
+                if (mr.wasMismatched()) {
+                    nummismatched++;
+                }
+                if (mr.isPermanent()) {
+                    numpermanent++;
+                } else if (mr.isLocked()) {
+                    numlocked++;
+                    if (trackLockSources && printlocksources) {
+                        for (Throwable th : mr.lockedFrom) {
+                            th.printStackTrace(System.err);
+                        }
+                        mr.lockedFrom.clear();
+                    }
+                }
+                if (mr.isInteresting()) {
+                    numinteresting++;
+                }
+            }
+        }
+
+        double avg_age = ((double) total_age) / total;
+        System.err.println(total+" total resources being managed");
+        System.err.println(String.format("average resource age is %.1f frames", avg_age));
+        printpoolpercent(numancient, total, "at maximum supported age");
+        printpoolpercent(numpermanent, total, "marked permanent");
+        printpoolpercent(nummismatched, total, "have had mismatched locks");
+        printpoolpercent(numlocked, total, "locked");
+        printpoolpercent(numinteresting, total, "contain interesting data");
+        printpoolpercent(numgone, total, "disappeared");
+        System.err.println();
+    }
+
+    private static void printpoolpercent(int stat, int total, String desc) {
+        double percent = stat * 100.0 / total;
+        String str = String.format("%,d resources %s (%.1f%%)", stat, desc, percent);
+        System.err.println(str);
+    }
+
     @Override
     public boolean isManagerThread() {
         return Thread.currentThread() == managerThread;
@@ -81,7 +343,7 @@
 
     @Override
     public boolean prepareForAllocation(long size) {
-        return ManagedResource.cleanup(this, size);
+        return cleanup(size);
     }
 
     @Override
@@ -90,13 +352,31 @@
     }
 
     @Override
-    public final void resourceManaged(T resource) {
-        recordAllocated(size(resource));
+    public final void resourceManaged(ManagedResource<T> mr) {
+        long size = size(mr.resource);
+        resourceHead.insert(mr, size);
+        recordAllocated(size);
     }
 
     @Override
-    public final void resourceFreed(T resource) {
-        recordFree(size(resource));
+    public final void resourceFreed(ManagedResource<T> freed) {
+        WeakLinkedList<T> prev = resourceHead;
+        WeakLinkedList<T> cur = prev.next;
+        while (cur != null) {
+            ManagedResource<T> res = cur.getResource();
+            if (res == null || res == freed) {
+                recordFree(cur.size);
+                cur = cur.next;
+                prev.next = cur;
+                if (res == freed) {
+                    return;
+                }
+            } else {
+                prev = cur;
+                cur = cur.next;
+            }
+        }
+        throw new IllegalStateException("unmanaged resource freed from pool "+this);
     }
 
     @Override
@@ -106,4 +386,29 @@
             throw new IllegalStateException("Negative resource amount");
         }
     }
+
+    static class WeakLinkedList<T> {
+        final WeakReference<ManagedResource<T>> theResourceRef;
+        final long size;
+        WeakLinkedList<T> next;
+
+        WeakLinkedList() {
+            this.theResourceRef = null;
+            this.size = 0L;
+        }
+
+        WeakLinkedList(ManagedResource<T> mresource, long size, WeakLinkedList<T> next) {
+            this.theResourceRef = new WeakReference<>(mresource);
+            this.size = size;
+            this.next = next;
+        }
+
+        void insert(ManagedResource<T> mresource, long size) {
+            this.next = new WeakLinkedList<>(mresource, size, next);
+        }
+
+        ManagedResource<T> getResource() {
+            return theResourceRef.get();
+        }
+    }
 }
--- a/modules/graphics/src/main/java/com/sun/prism/impl/ManagedResource.java	Mon Sep 08 15:47:52 2014 -0700
+++ b/modules/graphics/src/main/java/com/sun/prism/impl/ManagedResource.java	Mon Sep 08 15:58:17 2014 -0700
@@ -26,107 +26,12 @@
 package com.sun.prism.impl;
 
 import com.sun.prism.GraphicsResource;
-import java.lang.ref.WeakReference;
 import java.util.ArrayList;
-import java.util.HashMap;
 
 public abstract class ManagedResource<T> implements GraphicsResource {
-    private static final boolean trackLockSources = false;
-    // Number of calls to checkAndDispose() before we consider a resource to
-    // have not been used in a hypothetical "FOREVER".
-    private static final int FOREVER = 1024;
+    static final boolean trackLockSources = false;
 
-    private static WeakLinkedList resourceHead = new WeakLinkedList();
-
-    /* 
-     * Which resources to clean up in a call to {@link cleanup}.
-     */
-     public static enum PruningLevel {
-        /**
-         * Only resources that have been reclaimed through GC or other
-         * asynchronous disposal mechanisms should be pruned.
-         * This basically makes sure that our resource accounting is up
-         * to date.
-         */
-        OBSOLETE,
-
-        /**
-         * Only (unlocked, nonpermanent) resources that have not been used in
-         * a very long time should be pruned.
-         */
-        UNUSED_FOREVER,
-
-        /**
-         * Only (unlocked, nonpermanent) resources that hold no interesting
-         * data should be pruned.
-         */
-        UNINTERESTING,
-
-        /**
-         * Any resource that is unlocked and nonpermanent should be pruned.
-         */
-        ALL_UNLOCKED
-    }
-
-    /**
-     * Clean up the resources in the indicated pool according to the
-     * specified level of aggressiveness.
-     * 
-     * @param pool which pool to clean up resources from
-     * @param plevel how aggressively to clean up the resources
-     * @see PruningLevel
-     */
-    public static void cleanup(ResourcePool pool, PruningLevel plevel) {
-        cleanup(pool, plevel, FOREVER);
-    }
-
-    /**
-     * Clean up the resources in the indicated pool using a standard
-     * algorithm until at least the specified amount of resource units
-     * have been reclaimed.
-     * The standard algorithm uses the following stages until it obtains
-     * enough room in the pool:
-     * <ol>
-     * <li> Prune any resources which are already free, but have not been
-     *      accounted for yet.
-     * <li> Prune any resources that have not been used in a very long time.
-     * <li> Go through 2 more passes cleaning out any resources that have
-     *      not been used in a long time with decreasing cutoff limits for
-     *      the maximum age of the resource.
-     * <li> Finally, prune any resources that are not currently being used
-     *      (i.e. locked or permanent).
-     * </ol>
-     * 
-     * @param pool
-     * @param plevel
-     * @param needed
-     */
-    public static boolean cleanup(ResourcePool pool, long needed) {
-        if (pool.used() + needed <= pool.target()) return true;
-        cleanup(pool, PruningLevel.OBSOLETE, FOREVER);
-        if (pool.used() + needed <= pool.target()) return true;
-        cleanup(pool, PruningLevel.UNUSED_FOREVER, FOREVER);
-        if (pool.used() + needed <= pool.target()) return true;
-        cleanup(pool, PruningLevel.UNUSED_FOREVER, FOREVER/2);
-        if (pool.used() + needed <= pool.target()) return true;
-        cleanup(pool, PruningLevel.UNUSED_FOREVER, FOREVER/4);
-        if (pool.used() + needed <= pool.target()) return true;
-        cleanup(pool, PruningLevel.UNINTERESTING, FOREVER);
-        if (pool.used() + needed <= pool.target()) return true;
-        System.gc();
-        cleanup(pool, PruningLevel.ALL_UNLOCKED, FOREVER);
-        if (pool.used() + needed <= pool.max()) return true;
-        // Our alternative is to return false and cause an allocation
-        // failure which is usually bad news for any SG, so it is worth
-        // sleeping to give the GC some time to find a dead resource that
-        // was dropped on the floor...
-        System.gc();
-        try { Thread.sleep(20); } catch (InterruptedException e) { }
-        cleanup(pool, PruningLevel.ALL_UNLOCKED, FOREVER);
-        return (pool.used() + needed <= pool.max());
-    }
-
-    private static boolean _isgone(ManagedResource mr) {
+    static boolean _isgone(ManagedResource<?> mr) {
         if (mr == null) return true;
         if (mr.disposalRequested) {
             mr.free();
@@ -137,192 +42,6 @@
         return !mr.isValid();
     }
 
-    private static void cleanup(ResourcePool pool, PruningLevel plevel, int max_age) {
-        if (PrismSettings.poolDebug) {
-            switch (plevel) {
-                case OBSOLETE: System.err.print("Pruning"); break;
-                case UNUSED_FOREVER: System.err.print("Cleaning up unused in "+max_age); break;
-                case UNINTERESTING: System.err.print("Cleaning up uninteresting"); break;
-                case ALL_UNLOCKED: System.err.print("Aggressively cleaning up"); break;
-                default: throw new InternalError("Unrecognized pruning level: "+plevel);
-            }
-            System.err.println(" pool: "+pool);
-            printSummary(false);
-        }
-        long wasused = pool.used();
-        WeakLinkedList prev = resourceHead;
-        WeakLinkedList cur = prev.next;
-        while (cur != null) {
-            ManagedResource mr = cur.getResource();
-            if (_isgone(mr)) {
-                if (PrismSettings.poolDebug) {
-                    System.err.println("pruning: "+mr+" ("+cur.size+")"+
-                                       ((mr == null) ? "" :
-                                        ((mr.isPermanent() ? " perm" : "") +
-                                         (mr.isLocked() ? " lock" : "") +
-                                         (mr.isInteresting() ? " int" : ""))));
-                }
-                cur.pool.recordFree(cur.size);
-                cur = cur.next;
-                prev.next = cur;
-            } else if (plevel != PruningLevel.OBSOLETE &&
-                       mr.getPool() == pool &&
-                       !mr.isPermanent() &&
-                       !mr.isLocked() &&
-                       (plevel == PruningLevel.ALL_UNLOCKED ||
-                        (plevel == PruningLevel.UNINTERESTING && !mr.isInteresting()) ||
-                        (/* plevel == PruningLevel.UNUSED_FOREVER && */ mr.age >= max_age)))
-            {
-                if (PrismSettings.poolDebug) {
-                    System.err.println("disposing: "+mr+" ("+cur.size+") age="+mr.age+
-                                       (mr.isPermanent() ? " perm" : "") +
-                                       (mr.isLocked() ? " lock" : "") +
-                                       (mr.isInteresting() ? " int" : ""));
-                }
-                mr.free();
-                mr.resource = null;
-                cur.pool.recordFree(cur.size);
-                cur = cur.next;
-                prev.next = cur;
-            } else {
-                prev = cur;
-                cur = cur.next;
-            }
-        }
-        if (PrismSettings.poolDebug) {
-            long isused = pool.used();
-            System.err.println("cleaned up "+(wasused - isused)+" from pool: "+pool);
-            printSummary(false);
-        }
-    }
-
-    /**
-     * Check that all resources are in the correct state for an idle condition
-     * and free any resources which were disposed from a non-resource thread.
-     * This method must be called on a thread that is appropriate for disposing
-     * and managing resources for the resource pools.
-     * The boolean {@code forgiveStaleLocks} parameter is used to indicate that
-     * an exceptional condition occurred which caused the caller to abort a
-     * cycle of resource usage, potentially with outstanding resource locks.
-     * This method will unlock all non-permanent resources that have outstanding
-     * locks if {@code forgiveStaleLocks} is {@code true}, or it will print out
-     * a warning and a resource summary if that parameter is {@code false}.
-     * 
-     * @param forgiveStaleLocks {@code true} if the caller wishes to forgive
-     *         and unlock all outstanding locks on non-permanent resources
-     */
-    public static void freeDisposalRequestedAndCheckResources(boolean forgiveStaleLocks) {
-        boolean anyLockedResources = false;
-        WeakLinkedList prev = resourceHead;
-        WeakLinkedList cur = prev.next;
-        while (cur != null) {
-            ManagedResource mr = cur.getResource();
-            if (_isgone(mr)) {
-                cur.pool.recordFree(cur.size);
-                cur = cur.next;
-                prev.next = cur;
-            } else {
-                if (!mr.isPermanent()) {
-                    if (mr.isLocked() && !mr.mismatchDetected) {
-                        if (forgiveStaleLocks) {
-                            mr.unlockall();
-                        } else {
-                            mr.mismatchDetected = true;
-                            anyLockedResources = true;
-                        }
-                    }
-                    int age = mr.age;
-                    if (age < FOREVER) {
-                        mr.age = age + 1;
-                    }
-                }
-                prev = cur;
-                cur = cur.next;
-            }
-        }
-
-        if (PrismSettings.poolStats || anyLockedResources) {
-            if (anyLockedResources) {
-                System.err.println("Outstanding resource locks detected:");
-            }
-            ManagedResource.printSummary();
-        }
-    }
-
-    public static void printSummary() {
-        printSummary(true);
-    }
-
-    public static void printSummary(boolean printlocksources) {
-        int numgone = 0;
-        int numlocked = 0;
-        int numpermanent = 0;
-        int numinteresting = 0;
-        int nummismatched = 0;
-        int numancient = 0;
-        long total_age = 0;
-        int total = 0;
-        HashMap<ResourcePool, ResourcePool> poolsSeen = new HashMap<ResourcePool, ResourcePool>();
-        for (WeakLinkedList cur = resourceHead.next; cur != null; cur = cur.next) {
-            ManagedResource mr = cur.getResource();
-            total++;
-            if (mr == null || !mr.isValid() || mr.disposalRequested) {
-                numgone++;
-            } else {
-                ResourcePool pool = mr.getPool();
-                if (!poolsSeen.containsKey(pool)) {
-                    poolsSeen.put(pool, pool);
-                    double percentUsed = pool.used() * 100.0 / pool.max();
-                    double percentManaged = pool.managed() * 100.0 / pool.max();
-                    String str =
-                        String.format("%s: %,d used (%.1f%%), %,d managed (%.1f%%), %,d total",
-                                      pool, pool.used(), percentUsed,
-                                      pool.managed(), percentManaged,
-                                      pool.max());
-                    System.err.println(str);
-                }
-                total_age += mr.age;
-                if (mr.age >= FOREVER) {
-                    numancient++;
-                }
-                if (mr.mismatchDetected) {
-                    nummismatched++;
-                }
-                if (mr.isPermanent()) {
-                    numpermanent++;
-                } else if (mr.isLocked()) {
-                    numlocked++;
-                    if (trackLockSources && printlocksources) {
-                        ArrayList<Throwable> list = mr.lockedFrom;
-                        for (Throwable th : list) {
-                            th.printStackTrace(System.err);
-                        }
-                        mr.lockedFrom.clear();
-                    }
-                }
-                if (mr.isInteresting()) {
-                    numinteresting++;
-                }
-            }
-        }
-        double avg_age = ((double) total_age) / total;
-        System.err.println(total+" total resources being managed");
-        System.err.println(String.format("average resource age is %.1f frames", avg_age));
-        printpoolpercent(numancient, total, "at maximum supported age");
-        printpoolpercent(numpermanent, total, "marked permanent");
-        printpoolpercent(nummismatched, total, "have had mismatched locks");
-        printpoolpercent(numlocked, total, "locked");
-        printpoolpercent(numinteresting, total, "contain interesting data");
-        printpoolpercent(numgone, total, "disappeared");
-        System.err.println();
-    }
-
-    private static void printpoolpercent(int stat, int total, String desc) {
-        double percent = stat * 100.0 / total;
-        String str = String.format("%,d resources %s (%.1f%%)", stat, desc, percent);
-        System.err.println(str);
-    }
-
     protected T resource;
     private final ResourcePool<T> pool;
     private int lockcount;
@@ -330,7 +49,7 @@
     ArrayList<Throwable> lockedFrom;
     private boolean permanent;
     private boolean mismatchDetected;
-    private boolean disposalRequested = false;
+    private boolean disposalRequested;
     private int age;
 
     protected ManagedResource(T resource, ResourcePool<T> pool) {
@@ -339,31 +58,12 @@
         if (trackLockSources) {
             this.lockedFrom = new ArrayList<Throwable>();
         }
-        link();
+        manage();
         lock();
     }
 
-    private void link() {
-        resourceHead.insert(this);
-    }
-
-    private void unlink() {
-        WeakLinkedList prev = resourceHead;
-        WeakLinkedList cur = prev.next;
-        while (cur != null) {
-            ManagedResource mr = cur.getResource();
-            if (mr == null || mr == this) {
-                cur.pool.recordFree(cur.size);
-                cur = cur.next;
-                prev.next = cur;
-                if (mr == this) {
-                    break;
-                }
-            } else {
-                prev = cur;
-                cur = cur.next;
-            }
-        }
+    private void manage() {
+        pool.resourceManaged(this);
     }
 
     public final T getResource() {
@@ -371,7 +71,7 @@
         return resource;
     }
 
-    public final ResourcePool getPool() {
+    public final ResourcePool<T> getPool() {
         return pool;
     }
 
@@ -379,6 +79,10 @@
         return resource != null && !disposalRequested;
     }
 
+    public boolean isDisposalRequested() {
+        return disposalRequested;
+    }
+
     public final boolean isLocked() {
         return lockcount > 0;
     }
@@ -408,14 +112,19 @@
     public void free() {
     }
 
+    public int getAge() {
+        return age;
+    }
+
     @Override
     public final void dispose() {
         if (pool.isManagerThread()) {
-            if (resource != null) {
+            T r = resource;
+            if (r != null) {
                 free();
                 disposalRequested = false;
                 resource = null;
-                unlink();
+                pool.resourceFreed(this);
             }
         } else {
             disposalRequested = true;
@@ -436,7 +145,7 @@
         return resource;
     }
 
-    private void unlockall() {
+    void unlockall() {
         lockcount = 0;
         if (trackLockSources && !permanent) {
             lockedFrom.clear();
@@ -463,32 +172,18 @@
         employcount--;
     }
 
-    static class WeakLinkedList {
-        final WeakReference<ManagedResource> theResourceRef;
-        final ResourcePool pool;
-        final long size;
-        WeakLinkedList next;
+    public final boolean wasMismatched() {
+        return mismatchDetected;
+    }
 
-        WeakLinkedList() {
-            this.theResourceRef = null;
-            this.pool = null;
-            this.size = 0L;
-        }
+    public final void setMismatched() {
+        mismatchDetected = true;
+    }
 
-        WeakLinkedList(ManagedResource mresource, WeakLinkedList next) {
-            this.theResourceRef = new WeakReference<ManagedResource>(mresource);
-            this.pool = mresource.pool;
-            this.size = pool.size(mresource.resource);
-            pool.recordAllocated(size);
-            this.next = next;
-        }
-
-        void insert(ManagedResource resource) {
-            this.next = new WeakLinkedList(resource, next);
-        }
-
-        ManagedResource getResource() {
-            return theResourceRef.get();
+    public final void bumpAge(int forever) {
+        int a = this.age;
+        if (a < forever) {
+            this.age = a + 1;
         }
     }
 }
--- a/modules/graphics/src/main/java/com/sun/prism/impl/ResourcePool.java	Mon Sep 08 15:47:52 2014 -0700
+++ b/modules/graphics/src/main/java/com/sun/prism/impl/ResourcePool.java	Mon Sep 08 15:58:17 2014 -0700
@@ -52,18 +52,37 @@
  * The amounts and sizes returned from the methods should all be in the
  * same units, usually bytes.
  * 
+ * @param <T> the type of resource stored in this pool
  * @see ManagedResource
  */
 public interface ResourcePool<T> {
+    /**
+     * Check that all resources are in the correct state for an idle condition
+     * and free any resources which were disposed from a non-resource thread.
+     * This method must be called on a thread that is appropriate for disposing
+     * and managing resources for this resource pool.
+     * The boolean {@code forgiveStaleLocks} parameter is used to indicate that
+     * an exceptional condition occurred which caused the caller to abort a
+     * cycle of resource usage, potentially with outstanding resource locks.
+     * This method will unlock all non-permanent resources that have outstanding
+     * locks if {@code forgiveStaleLocks} is {@code true}, or it will print out
+     * a warning and a resource summary if that parameter is {@code false}.
+     * 
+     * @param forgiveStaleLocks {@code true} if the caller wishes to forgive
+     *         and unlock all outstanding locks on non-permanent resources
+     */
+    public void freeDisposalRequestedAndCheckResources(boolean forgiveStaleLocks);
 
     /**
      * True if Thread.currentThread() is a thread that created this ResourcePool
+     * @return true if Thread.currentThread() is a thread that created this ResourcePool
      */
     public boolean isManagerThread();
 
     /**
      * The amount of a resource currently being used to hold any kind of
      * resource, whether managed or not.
+     * @return the amount being used
      */
     public long used();
 
@@ -72,6 +91,7 @@
      * resources.
      * This amount may be less than the amount returned by the {@link used()}
      * method if the pool is shared amongst other resources.
+     * @return the amount being used to hold managed resources
      */
     public long managed();
 
@@ -79,6 +99,7 @@
      * The total space available in this pool for allocating any kind of
      * resource, even unmanaged resources, and including those resources
      * already allocated.
+     * @return the maximum amount of the resource
      */
     public long max();
 
@@ -87,6 +108,7 @@
      * should be used so as to be friendly to other parts of the system.
      * This number must be less than or equal to the amount returned by the
      * {@link max()} method.
+     * @return the typical target amount of the resource to be used
      */
     public long target();
 
@@ -115,20 +137,20 @@
     public void recordFree(long size);
 
     /**
-     * Record the indicated amount of the resource as being allocated for
-     * a {@link ManagedResource}.
+     * Record the {@link ManagedResource} object as being currently managed
+     * by this pool.
      * 
      * @param resource the resource that is now being managed
      */
-    public void resourceManaged(T resource);
+    public void resourceManaged(ManagedResource<T> resource);
 
     /**
-     * Record the indicated amount of the resource as no longer being
-     * held in a {@link ManagedResource}.
+     * Record the {@link ManagedResource} object as no longer being managed
+     * by this pool.
      * 
      * @param resource the resource that is freed, no longer being managed
      */
-    public void resourceFreed(T resource);
+    public void resourceFreed(ManagedResource<T> resource);
 
     /**
      * Prepare for an allocation of a resource from this pool of the
--- a/modules/graphics/src/main/java/com/sun/prism/j2d/print/J2DPrinterJob.java	Mon Sep 08 15:47:52 2014 -0700
+++ b/modules/graphics/src/main/java/com/sun/prism/j2d/print/J2DPrinterJob.java	Mon Sep 08 15:58:17 2014 -0700
@@ -1002,6 +1002,7 @@
             PrismPrintGraphics ppg =
                     new PrismPrintGraphics((Graphics2D) g, w, h);
             NGNode pgNode = node.impl_getPeer();
+            boolean errored = false;
             try {
                 pgNode.render(ppg);
             } catch (Throwable t) {
@@ -1009,7 +1010,11 @@
                     System.err.println("printNode caught exception.");
                     t.printStackTrace();
                 }
+                errored = true;
             }
+            ppg.getResourceFactory()
+                    .getTextureResourcePool()
+                    .freeDisposalRequestedAndCheckResources(errored);
         }
 
         public Printable getPrintable(int pageIndex) {