diff --git a/src/share/classes/java/lang/ref/Finalizer.java b/src/share/classes/java/lang/ref/Finalizer.java index 6ae2262c5aa2914d9c70358c4ef808195c4162db..5cc0ac76ae6212489aecb7a3ac15bff9a34c997b 100644 --- a/src/share/classes/java/lang/ref/Finalizer.java +++ b/src/share/classes/java/lang/ref/Finalizer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -82,6 +82,10 @@ final class Finalizer extends FinalReference { /* Package-private; must add(); } + static ReferenceQueue getQueue() { + return queue; + } + /* Invoked by VM */ static void register(Object finalizee) { new Finalizer(finalizee); diff --git a/src/share/classes/java/lang/ref/FinalizerHistogram.java b/src/share/classes/java/lang/ref/FinalizerHistogram.java new file mode 100644 index 0000000000000000000000000000000000000000..195eb483631e5c7c372326c3873d2d1f6fa84485 --- /dev/null +++ b/src/share/classes/java/lang/ref/FinalizerHistogram.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.lang.ref; + + +import java.util.Map; +import java.util.HashMap; +import java.util.Arrays; +import java.util.Comparator; + +/** + * This FinalizerHistogram class is for GC.finalizer_info diagnostic command support. + * It is invoked by the VM. + */ + +final class FinalizerHistogram { + + private static final class Entry { + private int instanceCount; + private final String className; + + int getInstanceCount() { + return instanceCount; + } + + void increment() { + instanceCount += 1; + } + + Entry(String className) { + this.className = className; + } + } + + // Method below is called by VM and VM expect certain + // entry class layout. + + static Entry[] getFinalizerHistogram() { + Map countMap = new HashMap<>(); + ReferenceQueue queue = Finalizer.getQueue(); + queue.forEach(r -> { + Object referent = r.get(); + if (referent != null) { + countMap.computeIfAbsent( + referent.getClass().getName(), Entry::new).increment(); + /* Clear stack slot containing this variable, to decrease + the chances of false retention with a conservative GC */ + referent = null; + } + }); + + Entry fhe[] = countMap.values().toArray(new Entry[countMap.size()]); + Arrays.sort(fhe, + Comparator.comparingInt(Entry::getInstanceCount).reversed()); + return fhe; + } +} diff --git a/src/share/classes/java/lang/ref/Reference.java b/src/share/classes/java/lang/ref/Reference.java index 5febcae029926df7832de20eb522c7f90e88a072..f8fa46f38481f9cc86f711ab258fe2cb53ca72af 100644 --- a/src/share/classes/java/lang/ref/Reference.java +++ b/src/share/classes/java/lang/ref/Reference.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -99,7 +99,7 @@ public abstract class Reference { * Inactive: this */ @SuppressWarnings("rawtypes") - Reference next; + volatile Reference next; /* When active: next element in a discovered reference list maintained by GC (or this if last) * pending: next element in the pending list (or null if last) diff --git a/src/share/classes/java/lang/ref/ReferenceQueue.java b/src/share/classes/java/lang/ref/ReferenceQueue.java index 534d21c7b2beb21e1a1f689fe4274fb97bbde7a9..59c5ea8b2c3710a4ef958b807d4f0e0b0549dd97 100644 --- a/src/share/classes/java/lang/ref/ReferenceQueue.java +++ b/src/share/classes/java/lang/ref/ReferenceQueue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,8 @@ package java.lang.ref; +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. @@ -75,13 +77,12 @@ public class ReferenceQueue { } } - @SuppressWarnings("unchecked") private Reference reallyPoll() { /* Must hold lock */ Reference r = head; if (r != null) { - head = (r.next == r) ? - null : - r.next; // Unchecked due to the next field having a raw type in Reference + @SuppressWarnings("unchecked") + Reference rn = r.next; + head = (rn == r) ? null : rn; r.queue = NULL; r.next = r; queueLength--; @@ -164,4 +165,32 @@ public class ReferenceQueue { 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); + @SuppressWarnings("unchecked") + Reference rn = r.next; + 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; + } + } + } } diff --git a/test/java/lang/ref/FinalizerHistogramTest.java b/test/java/lang/ref/FinalizerHistogramTest.java new file mode 100644 index 0000000000000000000000000000000000000000..cb570937617a77f40f344e13643015ab402aab12 --- /dev/null +++ b/test/java/lang/ref/FinalizerHistogramTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +import java.lang.reflect.Method; +import java.lang.reflect.Field; + +/* + * @test + * @summary Unit test for FinalizerHistogram + * @run main FinalizerHistogramTest + */ + +public class FinalizerHistogramTest { + static ReentrantLock lock = new ReentrantLock(); + static volatile int wasInitialized = 0; + static volatile int wasTrapped = 0; + static final int objectsCount = 1000; + + static class MyObject { + public MyObject() { + // Make sure object allocation/deallocation is not optimized out + wasInitialized += 1; + } + + protected void finalize() { + // Trap the object in a finalization queue + wasTrapped += 1; + lock.lock(); + } + } + + public static void main(String[] argvs) { + try { + lock.lock(); + for(int i = 0; i < objectsCount; ++i) { + new MyObject(); + } + System.out.println("Objects intialized: " + objectsCount); + System.gc(); + while(wasTrapped < 1); + + Class klass = Class.forName("java.lang.ref.FinalizerHistogram"); + + Method m = klass.getDeclaredMethod("getFinalizerHistogram"); + m.setAccessible(true); + Object entries[] = (Object[]) m.invoke(null); + + Class entryKlass = Class.forName("java.lang.ref.FinalizerHistogram$Entry"); + Field name = entryKlass.getDeclaredField("className"); + name.setAccessible(true); + Field count = entryKlass.getDeclaredField("instanceCount"); + count.setAccessible(true); + + System.out.println("Unreachable instances waiting for finalization"); + System.out.println("#instances class name"); + System.out.println("-----------------------"); + + boolean found = false; + for (Object entry : entries) { + Object e = entryKlass.cast(entry); + System.out.printf("%10d %s\n", count.get(e), name.get(e)); + if (((String) name.get(e)).indexOf("MyObject") != -1 ) { + found = true; + } + } + + if (!found) { + throw new RuntimeException("MyObject is not found in test output"); + } + + System.out.println("Test PASSED"); + } catch(Exception e) { + System.err.println("Test failed with " + e); + e.printStackTrace(System.err); + throw new RuntimeException("Test failed"); + } finally { + lock.unlock(); + } + } +}