--- old/src/java.base/share/classes/java/lang/ref/Finalizer.java 2015-05-14 09:44:15.211836252 +0200 +++ new/src/java.base/share/classes/java/lang/ref/Finalizer.java 2015-05-14 09:44:15.162836173 +0200 @@ -27,6 +27,10 @@ import java.security.PrivilegedAction; import java.security.AccessController; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + import sun.misc.JavaLangAccess; import sun.misc.ManagedLocalsThread; import sun.misc.SharedSecrets; @@ -186,6 +190,32 @@ }}}); } + static String printFinalizationQueue() { + Map countMap = new HashMap<>(); + queue.forEach(r -> { + Object referent = r.get(); + if (referent != null) { + countMap.computeIfAbsent( + referent.getClass().getName(), k -> new int[1])[0]++; + /* Clear stack slot containing this variable, to decrease + the chances of false retention with a conservative GC */ + referent = null; + } + }); + @SuppressWarnings("unchecked") + Map.Entry[] entries = countMap.entrySet().toArray( + new Map.Entry[countMap.size()] + ); + Arrays.sort(entries, (e1, e2) -> + Integer.compare(e2.getValue()[0], e1.getValue()[0])); + StringBuilder sb = new StringBuilder(); + for (Map.Entry e : entries) { + sb.append("Class: ").append(e.getKey()) + .append(" count: ").append(e.getValue()[0]).append("\n"); + } + return sb.toString(); + } + private static class FinalizerThread extends ManagedLocalsThread { private volatile boolean running; FinalizerThread(ThreadGroup g) { --- old/src/java.base/share/classes/java/lang/ref/ReferenceQueue.java 2015-05-14 09:44:15.402836559 +0200 +++ new/src/java.base/share/classes/java/lang/ref/ReferenceQueue.java 2015-05-14 09:44:15.353836480 +0200 @@ -25,6 +25,9 @@ package java.lang.ref; +import sun.misc.Unsafe; +import java.util.function.Consumer; + /** * Reference queues, to which registered reference objects are appended by the * garbage collector after the appropriate reachability changes are detected. @@ -83,6 +86,10 @@ null : r.next; // Unchecked due to the next field having a raw type in Reference r.queue = NULL; + // make sure the store to r.next below doesn't float up + // before the store to r.queue above because we need this + // for correct lock-less iteration in forEach() + U.storeFence(); r.next = r; queueLength--; if (r instanceof FinalReference) { @@ -164,4 +171,38 @@ return remove(0); } + /** + * Iterate queue and invoke given action with each Reference. + * Suitable for diagnostic purposes. + * WARNING: any use of this method should make sure to not + * retain the referents of iterated references (in case of + * FinalReference(s)) so that their life is not prolonged more + * than necessary. + */ + void forEach(Consumer> action) { + for (Reference r = head; r != null;) { + action.accept(r); + Reference rn = r.next; + // make sure load of r.queue below doesn't float up before + // the load of r.next above. The corresponding stores are + // performed in reallyPoll() in reverse order. + U.loadFence(); + if (rn == r) { + if (r.queue == ENQUEUED) { + // still enqueued -> we reached end of chain + r = null; + } else { + // already dequeued: r.queue == NULL; -> + // restart from head when overtaken by queue poller(s) + r = head; + } + } else { + // next in chain + r = rn; + } + } + } + + /* we need a fence */ + private static final Unsafe U = Unsafe.getUnsafe(); }