--- old/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java 2016-02-07 22:49:23.730535166 +0100 +++ new/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java 2016-02-07 22:49:23.615537211 +0100 @@ -30,7 +30,7 @@ import static java.lang.invoke.MethodHandleNatives.Constants.*; import static java.lang.invoke.MethodHandleStatics.*; import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; -import jdk.internal.ref.Cleaner; +import jdk.internal.ref.CleanerFactory; /** * The JVM interface for the method handles package is all here. @@ -71,7 +71,7 @@ // Cleaner is attached to CallSite instance and it clears native structures allocated for CallSite context. // Though the CallSite can become unreachable, its Context is retained by the Cleaner instance (which is // referenced from Cleaner class) until cleanup is performed. - Cleaner.create(cs, newContext); + CleanerFactory.cleaner().register(cs, newContext); return newContext; } --- old/src/java.base/share/classes/java/lang/ref/Cleaner.java 2016-02-07 22:49:24.034529759 +0100 +++ new/src/java.base/share/classes/java/lang/ref/Cleaner.java 2016-02-07 22:49:23.936531502 +0100 @@ -27,6 +27,7 @@ import java.util.Objects; import java.util.concurrent.ThreadFactory; +import java.util.function.Function; import jdk.internal.ref.CleanerImpl; @@ -135,7 +136,12 @@ final CleanerImpl impl; static { - CleanerImpl.setCleanerImplAccess((Cleaner c) -> c.impl); + CleanerImpl.setCleanerImplAccess(new Function() { + @Override + public CleanerImpl apply(Cleaner cleaner) { + return cleaner.impl; + } + }); } /** --- old/src/java.base/share/classes/java/lang/ref/Reference.java 2016-02-07 22:49:24.330524495 +0100 +++ new/src/java.base/share/classes/java/lang/ref/Reference.java 2016-02-07 22:49:24.227526327 +0100 @@ -29,7 +29,6 @@ import jdk.internal.HotSpotIntrinsicCandidate; import jdk.internal.misc.JavaLangRefAccess; import jdk.internal.misc.SharedSecrets; -import jdk.internal.ref.Cleaner; /** * Abstract base class for reference objects. This class defines the @@ -139,11 +138,10 @@ } static { - // pre-load and initialize InterruptedException and Cleaner classes + // pre-load and initialize InterruptedException class // so that we don't get into trouble later in the run loop if there's - // memory shortage while loading/initializing them lazily. + // memory shortage while loading/initializing it lazily. ensureClassInitialized(InterruptedException.class); - ensureClassInitialized(Cleaner.class); } ReferenceHandler(ThreadGroup g, String name) { @@ -175,14 +173,10 @@ */ static boolean tryHandlePending(boolean waitForNotify) { Reference r; - Cleaner c; try { synchronized (lock) { - if (pending != null) { - r = pending; - // 'instanceof' might throw OutOfMemoryError sometimes - // so do this before un-linking 'r' from the 'pending' chain... - c = r instanceof Cleaner ? (Cleaner) r : null; + r = pending; + if (r != null) { // unlink 'r' from 'pending' chain pending = r.discovered; r.discovered = null; @@ -209,12 +203,6 @@ return true; } - // Fast path for cleaners - if (c != null) { - c.clean(); - return true; - } - ReferenceQueue q = r.queue; if (q != ReferenceQueue.NULL) q.enqueue(r); return true; --- old/src/java.base/share/classes/java/nio/Bits.java 2016-02-07 22:49:24.609519533 +0100 +++ new/src/java.base/share/classes/java/nio/Bits.java 2016-02-07 22:49:24.521521098 +0100 @@ -32,6 +32,8 @@ import jdk.internal.misc.SharedSecrets; import jdk.internal.misc.Unsafe; import jdk.internal.misc.VM; +import jdk.internal.ref.CleanerFactory; +import jdk.internal.ref.CleanerImpl; /** * Access to bits, native and otherwise. @@ -629,7 +631,8 @@ // retry while helping enqueue pending Reference objects // which includes executing pending Cleaner(s) which includes // Cleaner(s) that free direct buffer memory - while (jlra.tryHandlePendingReference()) { + while (jlra.tryHandlePendingReference() | // use non-short-circuit or + CleanerImpl.getCleanerImpl(CleanerFactory.cleaner()).drainQueue()) { if (tryReserveMemory(size, cap)) { return; } @@ -651,7 +654,8 @@ if (sleeps >= MAX_SLEEPS) { break; } - if (!jlra.tryHandlePendingReference()) { + if (!jlra.tryHandlePendingReference() & // use non-short-circuit and + !CleanerImpl.getCleanerImpl(CleanerFactory.cleaner()).drainQueue()) { try { Thread.sleep(sleepTime); sleepTime <<= 1; --- old/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template 2016-02-07 22:49:24.823515727 +0100 +++ new/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template 2016-02-07 22:49:24.744517132 +0100 @@ -30,7 +30,8 @@ import java.io.FileDescriptor; import jdk.internal.misc.Unsafe; import jdk.internal.misc.VM; -import jdk.internal.ref.Cleaner; +import java.lang.ref.Cleaner; +import jdk.internal.ref.CleanerFactory; import sun.nio.ch.DirectBuffer; @@ -98,13 +99,13 @@ } - private final Cleaner cleaner; + private final Cleaner.Cleanable cleaner; - public Cleaner cleaner() { return cleaner; } + public Cleaner.Cleanable cleaner() { return cleaner; } #else[byte] - public Cleaner cleaner() { return null; } + public Cleaner.Cleanable cleaner() { return null; } #end[byte] @@ -136,7 +137,7 @@ } else { address = base; } - cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); + cleaner = CleanerFactory.cleaner().register(this, new Deallocator(base, size, cap)); att = null; #else[rw] super(cap); @@ -176,7 +177,9 @@ #if[rw] super(-1, 0, cap, cap, fd); address = addr; - cleaner = Cleaner.create(this, unmapper); + cleaner = (unmapper == null) + ? null + : CleanerFactory.cleaner().register(this, unmapper); att = null; #else[rw] super(cap, addr, fd, unmapper); --- old/src/java.base/share/classes/jdk/internal/ref/CleanerImpl.java 2016-02-07 22:49:25.044511796 +0100 +++ new/src/java.base/share/classes/jdk/internal/ref/CleanerImpl.java 2016-02-07 22:49:24.966513184 +0100 @@ -25,6 +25,8 @@ package jdk.internal.ref; +import sun.misc.InnocuousThread; + import java.lang.ref.Cleaner; import java.lang.ref.Cleaner.Cleanable; import java.lang.ref.ReferenceQueue; @@ -33,8 +35,6 @@ import java.util.concurrent.ThreadFactory; import java.util.function.Function; -import sun.misc.InnocuousThread; - /** * CleanerImpl manages a set of object references and corresponding cleaning actions. * CleanerImpl provides the functionality of {@link java.lang.ref.Cleaner}. @@ -76,7 +76,7 @@ * @param cleaner the cleaner * @return the corresponding CleanerImpl */ - static CleanerImpl getCleanerImpl(Cleaner cleaner) { + public static CleanerImpl getCleanerImpl(Cleaner cleaner) { return cleanerImplAccess.apply(cleaner); } @@ -103,7 +103,7 @@ } // schedule a nop cleaning action for the cleaner, so the associated thread // will continue to run at least until the cleaner is reclaimable. - new PhantomCleanableRef(cleaner, cleaner, () -> {}); + new CleanerCleanable(cleaner); if (threadFactory == null) { threadFactory = CleanerImpl.InnocuousThreadFactory.factory(); @@ -112,7 +112,12 @@ // now that there's at least one cleaning action, for the cleaner, // we can start the associated thread, which runs until // all cleaning actions have been run. - Thread thread = threadFactory.newThread(this::run); + Thread thread = threadFactory.newThread(new Runnable() { + @Override + public void run() { + CleanerImpl.this.run(); + } + }); thread.setDaemon(true); thread.start(); } @@ -128,7 +133,7 @@ * If the thread is a ManagedLocalsThread, the threadlocals * are erased before each cleanup */ - private void run() { + void run() { Thread t = Thread.currentThread(); InnocuousThread mlThread = (t instanceof InnocuousThread) ? (InnocuousThread) t @@ -147,15 +152,34 @@ if (ref != null) { ref.clean(); } - } catch (InterruptedException i) { - continue; // ignore the interruption } catch (Throwable e) { // ignore exceptions from the cleanup action + // (including interruption of cleanup thread) } } } /** + * Processes all Cleanable(s) that have been waiting in the queue. + * + * @return {@code true} if any Cleanable was found in the queue and + * was processed or {@code false} if the queue was empty. + */ + public boolean drainQueue() { + boolean cleaned = false; + Cleanable ref; + while ((ref = (Cleanable) queue.poll()) != null) { + try { + ref.clean(); + } catch (Throwable t) { + // ignore exceptions from the cleanup action + } + cleaned = true; + } + return cleaned; + } + + /** * Perform cleaning on an unreachable PhantomReference. */ public static final class PhantomCleanableRef extends PhantomCleanable { @@ -321,13 +345,29 @@ } public Thread newThread(Runnable r) { - return AccessController.doPrivileged((PrivilegedAction) () -> { - Thread t = new InnocuousThread(r); - t.setPriority(Thread.MAX_PRIORITY - 2); - t.setName("Cleaner-" + t.getId()); - return t; + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Thread run() { + Thread t = new InnocuousThread(r); + t.setPriority(Thread.MAX_PRIORITY - 2); + t.setName("Cleaner-" + t.getId()); + return t; + } }); } } + /** + * A PhantomCleanable implementation for tracking the Cleaner itself. + */ + static final class CleanerCleanable extends PhantomCleanable { + CleanerCleanable(Cleaner cleaner) { + super(cleaner, cleaner); + } + + @Override + protected void performCleanup() { + // no action + } + } } --- old/src/java.base/share/classes/sun/misc/InnocuousThread.java 2016-02-07 22:49:25.280507599 +0100 +++ new/src/java.base/share/classes/sun/misc/InnocuousThread.java 2016-02-07 22:49:25.180509378 +0100 @@ -128,8 +128,12 @@ } final ThreadGroup root = group; INNOCUOUSTHREADGROUP = AccessController.doPrivileged( - (PrivilegedAction) () -> - { return new ThreadGroup(root, "InnocuousThreadGroup"); }); + new PrivilegedAction() { + @Override + public ThreadGroup run() { + return new ThreadGroup(root, "InnocuousThreadGroup"); + } + }); } catch (Exception e) { throw new Error(e); } --- old/src/java.base/share/classes/sun/nio/ch/DirectBuffer.java 2016-02-07 22:49:25.509503527 +0100 +++ new/src/java.base/share/classes/sun/nio/ch/DirectBuffer.java 2016-02-07 22:49:25.420505109 +0100 @@ -25,8 +25,8 @@ package sun.nio.ch; -import jdk.internal.ref.Cleaner; +import java.lang.ref.Cleaner; public interface DirectBuffer { @@ -34,6 +34,6 @@ public Object attachment(); - public Cleaner cleaner(); + public Cleaner.Cleanable cleaner(); } --- old/src/java.base/share/classes/sun/nio/ch/FileChannelImpl.java 2016-02-07 22:49:25.751499223 +0100 +++ new/src/java.base/share/classes/sun/nio/ch/FileChannelImpl.java 2016-02-07 22:49:25.667500717 +0100 @@ -27,6 +27,7 @@ import java.io.FileDescriptor; import java.io.IOException; +import java.lang.ref.Cleaner; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.ClosedByInterruptException; @@ -47,7 +48,6 @@ import jdk.internal.misc.JavaIOFileDescriptorAccess; import jdk.internal.misc.JavaNioAccess; import jdk.internal.misc.SharedSecrets; -import jdk.internal.ref.Cleaner; import sun.security.action.GetPropertyAction; public class FileChannelImpl @@ -845,7 +845,7 @@ } private static void unmap(MappedByteBuffer bb) { - Cleaner cl = ((DirectBuffer)bb).cleaner(); + Cleaner.Cleanable cl = ((DirectBuffer)bb).cleaner(); if (cl != null) cl.clean(); } --- old/src/java.base/share/classes/sun/nio/ch/IOVecWrapper.java 2016-02-07 22:49:26.014494545 +0100 +++ new/src/java.base/share/classes/sun/nio/ch/IOVecWrapper.java 2016-02-07 22:49:25.928496075 +0100 @@ -26,7 +26,7 @@ package sun.nio.ch; import java.nio.ByteBuffer; -import jdk.internal.ref.Cleaner; +import jdk.internal.ref.CleanerFactory; /** @@ -101,7 +101,7 @@ } if (wrapper == null) { wrapper = new IOVecWrapper(size); - Cleaner.create(wrapper, new Deallocator(wrapper.vecArray)); + CleanerFactory.cleaner().register(wrapper, new Deallocator(wrapper.vecArray)); cached.set(wrapper); } return wrapper; --- old/src/java.base/share/classes/sun/nio/ch/Util.java 2016-02-07 22:49:26.314489210 +0100 +++ new/src/java.base/share/classes/sun/nio/ch/Util.java 2016-02-07 22:49:26.220490882 +0100 @@ -33,7 +33,6 @@ import java.security.PrivilegedAction; import java.util.*; import jdk.internal.misc.Unsafe; -import jdk.internal.ref.Cleaner; import sun.security.action.GetPropertyAction; --- old/src/java.base/share/classes/sun/nio/fs/NativeBuffer.java 2016-02-07 22:49:26.637483466 +0100 +++ new/src/java.base/share/classes/sun/nio/fs/NativeBuffer.java 2016-02-07 22:49:26.530485369 +0100 @@ -26,7 +26,9 @@ package sun.nio.fs; import jdk.internal.misc.Unsafe; -import jdk.internal.ref.Cleaner; +import jdk.internal.ref.CleanerFactory; + +import java.lang.ref.Cleaner; /** * A light-weight buffer in native memory. @@ -37,7 +39,7 @@ private final long address; private final int size; - private final Cleaner cleaner; + private final Cleaner.Cleanable cleanable; // optional "owner" to avoid copying // (only safe for use by thread-local caches) @@ -56,7 +58,7 @@ NativeBuffer(int size) { this.address = unsafe.allocateMemory(size); this.size = size; - this.cleaner = Cleaner.create(this, new Deallocator(address)); + this.cleanable = CleanerFactory.cleaner().register(this, new Deallocator(address)); } void release() { @@ -71,8 +73,8 @@ return size; } - Cleaner cleaner() { - return cleaner; + Cleaner.Cleanable cleanable() { + return cleanable; } // not synchronized; only safe for use by thread-local caches --- old/src/java.base/share/classes/sun/nio/fs/NativeBuffers.java 2016-02-07 22:49:26.903478735 +0100 +++ new/src/java.base/share/classes/sun/nio/fs/NativeBuffers.java 2016-02-07 22:49:26.823480157 +0100 @@ -107,14 +107,14 @@ for (int i=0; i Cleaners are a lightweight and more robust alternative to finalization. - * They are lightweight because they are not created by the VM and thus do not - * require a JNI upcall to be created, and because their cleanup code is - * invoked directly by the reference-handler thread rather than by the - * finalizer thread. They are more robust because they use phantom references, - * the weakest type of reference object, thereby avoiding the nasty ordering - * problems inherent to finalization. - * - *

A cleaner tracks a referent object and encapsulates a thunk of arbitrary - * cleanup code. Some time after the GC detects that a cleaner's referent has - * become phantom-reachable, the reference-handler thread will run the cleaner. - * Cleaners may also be invoked directly; they are thread safe and ensure that - * they run their thunks at most once. - * - *

Cleaners are not a replacement for finalization. They should be used - * only when the cleanup code is extremely simple and straightforward. - * Nontrivial cleaners are inadvisable since they risk blocking the - * reference-handler thread and delaying further cleanup and finalization. - * - * - * @author Mark Reinhold - */ - -public class Cleaner - extends PhantomReference - implements Runnable -{ - - // Dummy reference queue, needed because the PhantomReference constructor - // insists that we pass a queue. Nothing will ever be placed on this queue - // since the reference handler invokes cleaners explicitly. - // - private static final ReferenceQueue dummyQueue = new ReferenceQueue<>(); - - // Doubly-linked list of live cleaners, which prevents the cleaners - // themselves from being GC'd before their referents - // - private static Cleaner first = null; - - private Cleaner - next = null, - prev = null; - - private static synchronized Cleaner add(Cleaner cl) { - if (first != null) { - cl.next = first; - first.prev = cl; - } - first = cl; - return cl; - } - - private static synchronized boolean remove(Cleaner cl) { - - // If already removed, do nothing - if (cl.next == cl) - return false; - - // Update list - if (first == cl) { - if (cl.next != null) - first = cl.next; - else - first = cl.prev; - } - if (cl.next != null) - cl.next.prev = cl.prev; - if (cl.prev != null) - cl.prev.next = cl.next; - - // Indicate removal by pointing the cleaner to itself - cl.next = cl; - cl.prev = cl; - return true; - - } - - private final Runnable thunk; - - private Cleaner(Object referent, Runnable thunk) { - super(referent, dummyQueue); - this.thunk = thunk; - } - - /** - * Creates a new cleaner. - * - * @param ob the referent object to be cleaned - * @param thunk - * The cleanup code to be run when the cleaner is invoked. The - * cleanup code is run directly from the reference-handler thread, - * so it should be as simple and straightforward as possible. - * - * @return The new cleaner - */ - public static Cleaner create(Object ob, Runnable thunk) { - if (thunk == null) - return null; - return add(new Cleaner(ob, thunk)); - } - - /** - * Runs this cleaner, if it has not been run before. - */ - public void clean() { - if (!remove(this)) - return; - try { - thunk.run(); - } catch (final Throwable x) { - AccessController.doPrivileged(new PrivilegedAction<>() { - public Void run() { - if (System.err != null) - new Error("Cleaner terminated abnormally", x) - .printStackTrace(); - System.exit(1); - return null; - }}); - } - } - - @Override public void run() { - SecurityManager security = System.getSecurityManager(); - if (security != null) - security.checkPackageAccess("jdk.internal.ref"); - this.clean(); - } - -} --- old/test/jdk/internal/ref/Cleaner/ExitOnThrow.java 2016-02-07 22:49:27.649465467 +0100 +++ /dev/null 2016-02-07 16:58:02.522642765 +0100 @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2003, 2013, 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. - */ - -/* - * @test - * @bug 4954921 8009259 - * @library /test/lib/share/classes - * @build jdk.test.lib.* - * @build jdk.test.lib.process.* - * @run main ExitOnThrow - * @summary Ensure that if a cleaner throws an exception then the VM exits - */ -import java.util.Arrays; - -import jdk.internal.ref.Cleaner; -import jdk.test.lib.JDKToolLauncher; -import jdk.test.lib.process.OutputAnalyzer; -import jdk.test.lib.process.ProcessTools; - -public class ExitOnThrow { - - static final String cp = System.getProperty("test.classes", "."); - - public static void main(String[] args) throws Exception { - if (args.length == 0) { - String[] cmd = JDKToolLauncher.createUsingTestJDK("java") - .addToolArg("-cp") - .addToolArg(cp) - .addToolArg("ExitOnThrow") - .addToolArg("-executeCleaner") - .getCommand(); - ProcessBuilder pb = new ProcessBuilder(cmd); - OutputAnalyzer out = ProcessTools.executeProcess(pb); - System.out.println("======================"); - System.out.println(Arrays.toString(cmd)); - String msg = " stdout: [" + out.getStdout() + "]\n" + - " stderr: [" + out.getStderr() + "]\n" + - " exitValue = " + out.getExitValue() + "\n"; - System.out.println(msg); - - if (out.getExitValue() != 1) - throw new RuntimeException("Unexpected exit code: " + - out.getExitValue()); - - } else { - Cleaner.create(new Object(), - () -> { throw new RuntimeException("Foo!"); } ); - while (true) { - System.gc(); - Thread.sleep(100); - } - } - } - -}