8138696: java.lang.ref.Cleaner - an easy to use alternative to finalization
Reviewed-by: mchung, mr, chegar, plevart, kbarrett, dholmes
This commit is contained in:
parent
72bed9c5df
commit
197207b152
231
jdk/src/java.base/share/classes/java/lang/ref/Cleaner.java
Normal file
231
jdk/src/java.base/share/classes/java/lang/ref/Cleaner.java
Normal file
@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 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.Objects;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import jdk.internal.misc.CleanerImpl;
|
||||
|
||||
/**
|
||||
* {@code Cleaner} manages a set of object references and corresponding cleaning actions.
|
||||
* <p>
|
||||
* Cleaning actions are {@link #register(Object object, Runnable action) registered}
|
||||
* to run after the cleaner is notified that the object has become
|
||||
* phantom reachable.
|
||||
* The cleaner uses {@link PhantomReference} and {@link ReferenceQueue} to be
|
||||
* notified when the <a href="package-summary.html#reachability">reachability</a>
|
||||
* changes.
|
||||
* <p>
|
||||
* Each cleaner operates independently, managing the pending cleaning actions
|
||||
* and handling threading and termination when the cleaner is no longer in use.
|
||||
* Registering an object reference and corresponding cleaning action returns
|
||||
* a {@link Cleanable Cleanable}. The most efficient use is to explicitly invoke
|
||||
* the {@link Cleanable#clean clean} method when the object is closed or
|
||||
* no longer needed.
|
||||
* The cleaning action is a {@link Runnable} to be invoked at most once when
|
||||
* the object has become phantom reachable unless it has already been explicitly cleaned.
|
||||
* Note that the cleaning action must not refer to the object being registered.
|
||||
* If so, the object will not become phantom reachable and the cleaning action
|
||||
* will not be invoked automatically.
|
||||
* <p>
|
||||
* The execution of the cleaning action is performed
|
||||
* by a thread associated with the cleaner.
|
||||
* All exceptions thrown by the cleaning action are ignored.
|
||||
* The cleaner and other cleaning actions are not affected by
|
||||
* exceptions in a cleaning action.
|
||||
* The thread runs until all registered cleaning actions have
|
||||
* completed and the cleaner itself is reclaimed by the garbage collector.
|
||||
* <p>
|
||||
* The behavior of cleaners during {@link System#exit(int) System.exit}
|
||||
* is implementation specific. No guarantees are made relating
|
||||
* to whether cleaning actions are invoked or not.
|
||||
* <p>
|
||||
* Unless otherwise noted, passing a {@code null} argument to a constructor or
|
||||
* method in this class will cause a
|
||||
* {@link java.lang.NullPointerException NullPointerException} to be thrown.
|
||||
*
|
||||
* @apiNote
|
||||
* The cleaning action is invoked only after the associated object becomes
|
||||
* phantom reachable, so it is important that the object implementing the
|
||||
* cleaning action does not hold references to the object.
|
||||
* In this example, a static class encapsulates the cleaning state and action.
|
||||
* An "inner" class, anonymous or not, must not be used because it implicitly
|
||||
* contains a reference to the outer instance, preventing it from becoming
|
||||
* phantom reachable.
|
||||
* The choice of a new cleaner or sharing an existing cleaner is determined
|
||||
* by the use case.
|
||||
* <p>
|
||||
* If the CleaningExample is used in a try-finally block then the
|
||||
* {@code close} method calls the cleaning action.
|
||||
* If the {@code close} method is not called, the cleaning action is called
|
||||
* by the Cleaner when the CleaningExample instance has become phantom reachable.
|
||||
* <pre>{@code
|
||||
* public class CleaningExample implements AutoCloseable {
|
||||
* // A cleaner, preferably one shared within a library
|
||||
* private static final Cleaner cleaner = <cleaner>;
|
||||
*
|
||||
* static class State implements Runnable {
|
||||
*
|
||||
* State(...) {
|
||||
* // initialize State needed for cleaning action
|
||||
* }
|
||||
*
|
||||
* public void run() {
|
||||
* // cleanup action accessing State, executed at most once
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* private final State;
|
||||
* private final Cleaner.Cleanable cleanable
|
||||
*
|
||||
* public CleaningExample() {
|
||||
* this.state = new State(...);
|
||||
* this.cleanable = cleaner.register(this, state);
|
||||
* }
|
||||
*
|
||||
* public void close() {
|
||||
* cleanable.clean();
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
* The cleaning action could be a lambda but all too easily will capture
|
||||
* the object reference, by referring to fields of the object being cleaned,
|
||||
* preventing the object from becoming phantom reachable.
|
||||
* Using a static nested class, as above, will avoid accidentally retaining the
|
||||
* object reference.
|
||||
* <p>
|
||||
* <a name="compatible-cleaners"></a>
|
||||
* Cleaning actions should be prepared to be invoked concurrently with
|
||||
* other cleaning actions.
|
||||
* Typically the cleaning actions should be very quick to execute
|
||||
* and not block. If the cleaning action blocks, it may delay processing
|
||||
* other cleaning actions registered to the same cleaner.
|
||||
* All cleaning actions registered to a cleaner should be mutually compatible.
|
||||
* @since 9
|
||||
*/
|
||||
public final class Cleaner {
|
||||
|
||||
/**
|
||||
* The Cleaner implementation.
|
||||
*/
|
||||
final CleanerImpl impl;
|
||||
|
||||
static {
|
||||
CleanerImpl.setCleanerImplAccess((Cleaner c) -> c.impl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a Cleaner implementation and start it.
|
||||
*/
|
||||
private Cleaner() {
|
||||
impl = new CleanerImpl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@code Cleaner}.
|
||||
* <p>
|
||||
* The cleaner creates a {@link Thread#setDaemon(boolean) daemon thread}
|
||||
* to process the phantom reachable objects and to invoke cleaning actions.
|
||||
* The {@linkplain java.lang.Thread#getContextClassLoader context class loader}
|
||||
* of the thread is set to the
|
||||
* {@link ClassLoader#getSystemClassLoader() system class loader}.
|
||||
* The thread has no permissions, enforced only if a
|
||||
* {@link java.lang.System#setSecurityManager(SecurityManager) SecurityManager is set}.
|
||||
* <p>
|
||||
* The cleaner terminates when it is phantom reachable and all of the
|
||||
* registered cleaning actions are complete.
|
||||
*
|
||||
* @return a new {@code Cleaner}
|
||||
*
|
||||
* @throws SecurityException if the current thread is not allowed to
|
||||
* create or start the thread.
|
||||
*/
|
||||
public static Cleaner create() {
|
||||
Cleaner cleaner = new Cleaner();
|
||||
cleaner.impl.start(cleaner, null);
|
||||
return cleaner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@code Cleaner} using a {@code Thread} from the {@code ThreadFactory}.
|
||||
* <p>
|
||||
* A thread from the thread factory's {@link ThreadFactory#newThread(Runnable) newThread}
|
||||
* method is set to be a {@link Thread#setDaemon(boolean) daemon thread}
|
||||
* and started to process phantom reachable objects and invoke cleaning actions.
|
||||
* On each call the {@link ThreadFactory#newThread(Runnable) thread factory}
|
||||
* must provide a Thread that is suitable for performing the cleaning actions.
|
||||
* <p>
|
||||
* The cleaner terminates when it is phantom reachable and all of the
|
||||
* registered cleaning actions are complete.
|
||||
*
|
||||
* @param threadFactory a {@code ThreadFactory} to return a new {@code Thread}
|
||||
* to process cleaning actions
|
||||
* @return a new {@code Cleaner}
|
||||
*
|
||||
* @throws IllegalThreadStateException if the thread from the thread
|
||||
* factory was {@link Thread.State#NEW not a new thread}.
|
||||
* @throws SecurityException if the current thread is not allowed to
|
||||
* create or start the thread.
|
||||
*/
|
||||
public static Cleaner create(ThreadFactory threadFactory) {
|
||||
Objects.requireNonNull(threadFactory, "threadFactory");
|
||||
Cleaner cleaner = new Cleaner();
|
||||
cleaner.impl.start(cleaner, threadFactory);
|
||||
return cleaner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an object and a cleaning action to run when the object
|
||||
* becomes phantom reachable.
|
||||
* Refer to the <a href="#compatible-cleaners">API Note</a> above for
|
||||
* cautions about the behavior of cleaning actions.
|
||||
*
|
||||
* @param obj the object to monitor
|
||||
* @param action a {@code Runnable} to invoke when the object becomes phantom reachable
|
||||
* @return a {@code Cleanable} instance
|
||||
*/
|
||||
public Cleanable register(Object obj, Runnable action) {
|
||||
Objects.requireNonNull(obj, "obj");
|
||||
Objects.requireNonNull(action, "action");
|
||||
return new CleanerImpl.PhantomCleanableRef(obj, this, action);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code Cleanable} represents an object and a
|
||||
* cleaning action registered in a {@code Cleaner}.
|
||||
* @since 9
|
||||
*/
|
||||
public interface Cleanable {
|
||||
/**
|
||||
* Unregisters the cleanable and invokes the cleaning action.
|
||||
* The cleanable's cleaning action is invoked at most once
|
||||
* regardless of the number of calls to {@code clean}.
|
||||
*/
|
||||
void clean();
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1998, 2003, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1998, 2015, 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
|
||||
@ -45,6 +45,8 @@
|
||||
* (or values) from being reclaimed, and phantom references are for
|
||||
* scheduling pre-mortem cleanup actions in a more flexible way than
|
||||
* is possible with the Java finalization mechanism.
|
||||
* Post-mortem cleanup actions can be registered and managed by a
|
||||
* {@link java.lang.ref.Cleaner}.
|
||||
*
|
||||
* <p> Each reference-object type is implemented by a subclass of the
|
||||
* abstract base {@link java.lang.ref.Reference} class.
|
||||
|
@ -0,0 +1,790 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 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 jdk.internal.misc;
|
||||
|
||||
import java.lang.ref.Cleaner;
|
||||
import java.lang.ref.Cleaner.Cleanable;
|
||||
import java.lang.ref.PhantomReference;
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.function.Function;
|
||||
|
||||
import sun.misc.InnocuousThread;
|
||||
import sun.misc.ManagedLocalsThread;
|
||||
|
||||
/**
|
||||
* CleanerImpl manages a set of object references and corresponding cleaning actions.
|
||||
* CleanerImpl provides the functionality of {@link java.lang.ref.Cleaner}.
|
||||
*/
|
||||
public final class CleanerImpl implements Runnable {
|
||||
|
||||
/**
|
||||
* An object to access the CleanerImpl from a Cleaner; set by Cleaner init.
|
||||
*/
|
||||
private static Function<Cleaner, CleanerImpl> cleanerImplAccess = null;
|
||||
|
||||
/**
|
||||
* Heads of a CleanableList for each reference type.
|
||||
*/
|
||||
final PhantomCleanable<?> phantomCleanableList;
|
||||
|
||||
final WeakCleanable<?> weakCleanableList;
|
||||
|
||||
final SoftCleanable<?> softCleanableList;
|
||||
|
||||
// The ReferenceQueue of pending cleaning actions
|
||||
final ReferenceQueue<Object> queue;
|
||||
|
||||
/**
|
||||
* Called by Cleaner static initialization to provide the function
|
||||
* to map from Cleaner to CleanerImpl.
|
||||
* @param access a function to map from Cleaner to CleanerImpl
|
||||
*/
|
||||
public static void setCleanerImplAccess(Function<Cleaner, CleanerImpl> access) {
|
||||
if (cleanerImplAccess == null) {
|
||||
cleanerImplAccess = access;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to get the CleanerImpl for a Cleaner.
|
||||
* @param cleaner the cleaner
|
||||
* @return the corresponding CleanerImpl
|
||||
*/
|
||||
private static CleanerImpl getCleanerImpl(Cleaner cleaner) {
|
||||
return cleanerImplAccess.apply(cleaner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for CleanerImpl.
|
||||
*/
|
||||
public CleanerImpl() {
|
||||
queue = new ReferenceQueue<>();
|
||||
phantomCleanableList = new PhantomCleanableRef(this);
|
||||
weakCleanableList = new WeakCleanableRef(this);
|
||||
softCleanableList = new SoftCleanableRef(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the Cleaner implementation.
|
||||
* When started waits for Cleanables to be queued.
|
||||
* @param service the cleaner
|
||||
* @param threadFactory the thread factory
|
||||
*/
|
||||
public void start(Cleaner service, ThreadFactory threadFactory) {
|
||||
// schedule a nop cleaning action for the service, so the associated thread
|
||||
// will continue to run at least until the service is reclaimable.
|
||||
new PhantomCleanableRef(service, service, () -> {});
|
||||
|
||||
if (threadFactory == null) {
|
||||
threadFactory = CleanerImpl.InnocuousThreadFactory.factory();
|
||||
}
|
||||
|
||||
// now that there's at least one cleaning action, for the service,
|
||||
// we can start the associated thread, which runs until
|
||||
// all cleaning actions have been run.
|
||||
Thread thread = threadFactory.newThread(this);
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process queued Cleanables as long as the cleanable lists are not empty.
|
||||
* A Cleanable is in one of the lists for each Object and for the Cleaner
|
||||
* itself.
|
||||
* Terminates when the Cleaner is no longer reachable and
|
||||
* has been cleaned and there are no more Cleanable instances
|
||||
* for which the object is reachable.
|
||||
* <p>
|
||||
* If the thread is a ManagedLocalsThread, the threadlocals
|
||||
* are erased before each cleanup
|
||||
*/
|
||||
public void run() {
|
||||
Thread t = Thread.currentThread();
|
||||
ManagedLocalsThread mlThread = (t instanceof ManagedLocalsThread)
|
||||
? (ManagedLocalsThread) t
|
||||
: null;
|
||||
while (!phantomCleanableList.isListEmpty() ||
|
||||
!weakCleanableList.isListEmpty() ||
|
||||
!softCleanableList.isListEmpty()) {
|
||||
if (mlThread != null) {
|
||||
// Clear the thread locals
|
||||
mlThread.eraseThreadLocals();
|
||||
}
|
||||
try {
|
||||
// Wait for a Ref, with a timeout to avoid getting hung
|
||||
// due to a race with clear/clean
|
||||
Cleanable ref = (Cleanable) queue.remove(60 * 1000L);
|
||||
if (ref != null) {
|
||||
ref.clean();
|
||||
}
|
||||
} catch (InterruptedException i) {
|
||||
continue; // ignore the interruption
|
||||
} catch (Throwable e) {
|
||||
// ignore exceptions from the cleanup action
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PhantomCleanable subclasses efficiently encapsulate cleanup state and
|
||||
* the cleaning action.
|
||||
* Subclasses implement the abstract {@link #performCleanup()} method
|
||||
* to provide the cleaning action.
|
||||
* When constructed, the object reference and the {@link Cleanable Cleanable}
|
||||
* are registered with the {@link Cleaner}.
|
||||
* The Cleaner invokes {@link Cleaner.Cleanable#clean() clean} after the
|
||||
* referent becomes phantom reachable.
|
||||
*/
|
||||
public static abstract class PhantomCleanable<T> extends PhantomReference<T>
|
||||
implements Cleaner.Cleanable {
|
||||
|
||||
/**
|
||||
* Links to previous and next in a doubly-linked list.
|
||||
*/
|
||||
PhantomCleanable<?> prev = this, next = this;
|
||||
|
||||
/**
|
||||
* The CleanerImpl for this Cleanable.
|
||||
*/
|
||||
private final CleanerImpl cleanerImpl;
|
||||
|
||||
/**
|
||||
* Constructs new {@code PhantomCleanable} with
|
||||
* {@code non-null referent} and {@code non-null cleaner}.
|
||||
* The {@code cleaner} is not retained; it is only used to
|
||||
* register the newly constructed {@link Cleaner.Cleanable Cleanable}.
|
||||
*
|
||||
* @param referent the referent to track
|
||||
* @param cleaner the {@code Cleaner} to register with
|
||||
*/
|
||||
public PhantomCleanable(T referent, Cleaner cleaner) {
|
||||
super(Objects.requireNonNull(referent), getCleanerImpl(cleaner).queue);
|
||||
this.cleanerImpl = getCleanerImpl(cleaner);
|
||||
insert();
|
||||
|
||||
// TODO: Replace getClass() with ReachabilityFence when it is available
|
||||
cleaner.getClass();
|
||||
referent.getClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new root of the list; not inserted.
|
||||
*/
|
||||
PhantomCleanable(CleanerImpl cleanerImpl) {
|
||||
super(null, null);
|
||||
this.cleanerImpl = cleanerImpl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert this PhantomCleanable after the list head.
|
||||
*/
|
||||
private void insert() {
|
||||
final PhantomCleanable<?> list = cleanerImpl.phantomCleanableList;
|
||||
synchronized (list) {
|
||||
prev = list;
|
||||
next = list.next;
|
||||
next.prev = this;
|
||||
list.next = this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this PhantomCleanable from the list.
|
||||
*
|
||||
* @return true if Cleanable was removed or false if not because
|
||||
* it had already been removed before
|
||||
*/
|
||||
private boolean remove() {
|
||||
PhantomCleanable<?> list = cleanerImpl.phantomCleanableList;
|
||||
synchronized (list) {
|
||||
if (next != this) {
|
||||
next.prev = prev;
|
||||
prev.next = next;
|
||||
prev = this;
|
||||
next = this;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the list's next reference refers to itself.
|
||||
*
|
||||
* @return true if the list is empty
|
||||
*/
|
||||
boolean isListEmpty() {
|
||||
PhantomCleanable<?> list = cleanerImpl.phantomCleanableList;
|
||||
synchronized (list) {
|
||||
return list == list.next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister this PhantomCleanable and invoke {@link #performCleanup()},
|
||||
* ensuring at-most-once semantics.
|
||||
*/
|
||||
@Override
|
||||
public final void clean() {
|
||||
if (remove()) {
|
||||
super.clear();
|
||||
performCleanup();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister this PhantomCleanable and clear the reference.
|
||||
* Due to inherent concurrency, {@link #performCleanup()} may still be invoked.
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
if (remove()) {
|
||||
super.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@code performCleanup} abstract method is overridden
|
||||
* to implement the cleaning logic.
|
||||
* The {@code performCleanup} method should not be called except
|
||||
* by the {@link #clean} method which ensures at most once semantics.
|
||||
*/
|
||||
protected abstract void performCleanup();
|
||||
|
||||
/**
|
||||
* This method always throws {@link UnsupportedOperationException}.
|
||||
* Enqueuing details of {@link Cleaner.Cleanable}
|
||||
* are a private implementation detail.
|
||||
*
|
||||
* @throws UnsupportedOperationException always
|
||||
*/
|
||||
@Override
|
||||
public final boolean isEnqueued() {
|
||||
throw new UnsupportedOperationException("isEnqueued");
|
||||
}
|
||||
|
||||
/**
|
||||
* This method always throws {@link UnsupportedOperationException}.
|
||||
* Enqueuing details of {@link Cleaner.Cleanable}
|
||||
* are a private implementation detail.
|
||||
*
|
||||
* @throws UnsupportedOperationException always
|
||||
*/
|
||||
@Override
|
||||
public final boolean enqueue() {
|
||||
throw new UnsupportedOperationException("enqueue");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WeakCleanable subclasses efficiently encapsulate cleanup state and
|
||||
* the cleaning action.
|
||||
* Subclasses implement the abstract {@link #performCleanup()} method
|
||||
* to provide the cleaning action.
|
||||
* When constructed, the object reference and the {@link Cleanable Cleanable}
|
||||
* are registered with the {@link Cleaner}.
|
||||
* The Cleaner invokes {@link Cleaner.Cleanable#clean() clean} after the
|
||||
* referent becomes weakly reachable.
|
||||
*/
|
||||
public static abstract class WeakCleanable<T> extends WeakReference<T>
|
||||
implements Cleaner.Cleanable {
|
||||
|
||||
/**
|
||||
* Links to previous and next in a doubly-linked list.
|
||||
*/
|
||||
WeakCleanable<?> prev = this, next = this;
|
||||
|
||||
/**
|
||||
* The CleanerImpl for this Cleanable.
|
||||
*/
|
||||
private final CleanerImpl cleanerImpl;
|
||||
|
||||
/**
|
||||
* Constructs new {@code WeakCleanableReference} with
|
||||
* {@code non-null referent} and {@code non-null cleaner}.
|
||||
* The {@code cleaner} is not retained by this reference; it is only used
|
||||
* to register the newly constructed {@link Cleaner.Cleanable Cleanable}.
|
||||
*
|
||||
* @param referent the referent to track
|
||||
* @param cleaner the {@code Cleaner} to register new reference with
|
||||
*/
|
||||
public WeakCleanable(T referent, Cleaner cleaner) {
|
||||
super(Objects.requireNonNull(referent), getCleanerImpl(cleaner).queue);
|
||||
cleanerImpl = getCleanerImpl(cleaner);
|
||||
insert();
|
||||
|
||||
// TODO: Replace getClass() with ReachabilityFence when it is available
|
||||
cleaner.getClass();
|
||||
referent.getClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new root of the list; not inserted.
|
||||
*/
|
||||
WeakCleanable(CleanerImpl cleanerImpl) {
|
||||
super(null, null);
|
||||
this.cleanerImpl = cleanerImpl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert this WeakCleanableReference after the list head.
|
||||
*/
|
||||
private void insert() {
|
||||
final WeakCleanable<?> list = cleanerImpl.weakCleanableList;
|
||||
synchronized (list) {
|
||||
prev = list;
|
||||
next = list.next;
|
||||
next.prev = this;
|
||||
list.next = this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this WeakCleanableReference from the list.
|
||||
*
|
||||
* @return true if Cleanable was removed or false if not because
|
||||
* it had already been removed before
|
||||
*/
|
||||
private boolean remove() {
|
||||
WeakCleanable<?> list = cleanerImpl.weakCleanableList;
|
||||
synchronized (list) {
|
||||
if (next != this) {
|
||||
next.prev = prev;
|
||||
prev.next = next;
|
||||
prev = this;
|
||||
next = this;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the list's next reference refers to itself.
|
||||
*
|
||||
* @return true if the list is empty
|
||||
*/
|
||||
boolean isListEmpty() {
|
||||
WeakCleanable<?> list = cleanerImpl.weakCleanableList;
|
||||
synchronized (list) {
|
||||
return list == list.next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister this WeakCleanable reference and invoke {@link #performCleanup()},
|
||||
* ensuring at-most-once semantics.
|
||||
*/
|
||||
@Override
|
||||
public final void clean() {
|
||||
if (remove()) {
|
||||
super.clear();
|
||||
performCleanup();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister this WeakCleanable and clear the reference.
|
||||
* Due to inherent concurrency, {@link #performCleanup()} may still be invoked.
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
if (remove()) {
|
||||
super.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@code performCleanup} abstract method is overridden
|
||||
* to implement the cleaning logic.
|
||||
* The {@code performCleanup} method should not be called except
|
||||
* by the {@link #clean} method which ensures at most once semantics.
|
||||
*/
|
||||
protected abstract void performCleanup();
|
||||
|
||||
/**
|
||||
* This method always throws {@link UnsupportedOperationException}.
|
||||
* Enqueuing details of {@link java.lang.ref.Cleaner.Cleanable}
|
||||
* are a private implementation detail.
|
||||
*
|
||||
* @throws UnsupportedOperationException always
|
||||
*/
|
||||
@Override
|
||||
public final boolean isEnqueued() {
|
||||
throw new UnsupportedOperationException("isEnqueued");
|
||||
}
|
||||
|
||||
/**
|
||||
* This method always throws {@link UnsupportedOperationException}.
|
||||
* Enqueuing details of {@link java.lang.ref.Cleaner.Cleanable}
|
||||
* are a private implementation detail.
|
||||
*
|
||||
* @throws UnsupportedOperationException always
|
||||
*/
|
||||
@Override
|
||||
public final boolean enqueue() {
|
||||
throw new UnsupportedOperationException("enqueue");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SoftCleanable subclasses efficiently encapsulate cleanup state and
|
||||
* the cleaning action.
|
||||
* Subclasses implement the abstract {@link #performCleanup()} method
|
||||
* to provide the cleaning action.
|
||||
* When constructed, the object reference and the {@link Cleanable Cleanable}
|
||||
* are registered with the {@link Cleaner}.
|
||||
* The Cleaner invokes {@link Cleaner.Cleanable#clean() clean} after the
|
||||
* referent becomes softly reachable.
|
||||
*/
|
||||
public static abstract class SoftCleanable<T> extends SoftReference<T>
|
||||
implements Cleaner.Cleanable {
|
||||
|
||||
/**
|
||||
* Links to previous and next in a doubly-linked list.
|
||||
*/
|
||||
SoftCleanable<?> prev = this, next = this;
|
||||
|
||||
/**
|
||||
* The CleanerImpl for this Cleanable.
|
||||
*/
|
||||
private final CleanerImpl cleanerImpl;
|
||||
|
||||
/**
|
||||
* Constructs new {@code SoftCleanableReference} with
|
||||
* {@code non-null referent} and {@code non-null cleaner}.
|
||||
* The {@code cleaner} is not retained by this reference; it is only used
|
||||
* to register the newly constructed {@link Cleaner.Cleanable Cleanable}.
|
||||
*
|
||||
* @param referent the referent to track
|
||||
* @param cleaner the {@code Cleaner} to register with
|
||||
*/
|
||||
public SoftCleanable(T referent, Cleaner cleaner) {
|
||||
super(Objects.requireNonNull(referent), getCleanerImpl(cleaner).queue);
|
||||
cleanerImpl = getCleanerImpl(cleaner);
|
||||
insert();
|
||||
|
||||
// TODO: Replace getClass() with ReachabilityFence when it is available
|
||||
cleaner.getClass();
|
||||
referent.getClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new root of the list; not inserted.
|
||||
*/
|
||||
SoftCleanable(CleanerImpl cleanerImpl) {
|
||||
super(null, null);
|
||||
this.cleanerImpl = cleanerImpl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert this SoftCleanableReference after the list head.
|
||||
*/
|
||||
private void insert() {
|
||||
final SoftCleanable<?> list = cleanerImpl.softCleanableList;
|
||||
synchronized (list) {
|
||||
prev = list;
|
||||
next = list.next;
|
||||
next.prev = this;
|
||||
list.next = this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this SoftCleanableReference from the list.
|
||||
*
|
||||
* @return true if Cleanable was removed or false if not because
|
||||
* it had already been removed before
|
||||
*/
|
||||
private boolean remove() {
|
||||
SoftCleanable<?> list = cleanerImpl.softCleanableList;
|
||||
synchronized (list) {
|
||||
if (next != this) {
|
||||
next.prev = prev;
|
||||
prev.next = next;
|
||||
prev = this;
|
||||
next = this;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the list's next reference refers to itself.
|
||||
*
|
||||
* @return true if the list is empty
|
||||
*/
|
||||
boolean isListEmpty() {
|
||||
SoftCleanable<?> list = cleanerImpl.softCleanableList;
|
||||
synchronized (list) {
|
||||
return list == list.next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister this SoftCleanable reference and invoke {@link #performCleanup()},
|
||||
* ensuring at-most-once semantics.
|
||||
*/
|
||||
@Override
|
||||
public final void clean() {
|
||||
if (remove()) {
|
||||
super.clear();
|
||||
performCleanup();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister this SoftCleanable and clear the reference.
|
||||
* Due to inherent concurrency, {@link #performCleanup()} may still be invoked.
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
if (remove()) {
|
||||
super.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@code performCleanup} abstract method is overridden
|
||||
* to implement the cleaning logic.
|
||||
* The {@code performCleanup} method should not be called except
|
||||
* by the {@link #clean} method which ensures at most once semantics.
|
||||
*/
|
||||
protected abstract void performCleanup();
|
||||
|
||||
/**
|
||||
* This method always throws {@link UnsupportedOperationException}.
|
||||
* Enqueuing details of {@link Cleaner.Cleanable}
|
||||
* are a private implementation detail.
|
||||
*
|
||||
* @throws UnsupportedOperationException always
|
||||
*/
|
||||
@Override
|
||||
public final boolean isEnqueued() {
|
||||
throw new UnsupportedOperationException("isEnqueued");
|
||||
}
|
||||
|
||||
/**
|
||||
* This method always throws {@link UnsupportedOperationException}.
|
||||
* Enqueuing details of {@link Cleaner.Cleanable}
|
||||
* are a private implementation detail.
|
||||
*
|
||||
* @throws UnsupportedOperationException always
|
||||
*/
|
||||
@Override
|
||||
public final boolean enqueue() {
|
||||
throw new UnsupportedOperationException("enqueue");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform cleaning on an unreachable PhantomReference.
|
||||
*/
|
||||
public static final class PhantomCleanableRef extends PhantomCleanable<Object> {
|
||||
private final Runnable action;
|
||||
|
||||
/**
|
||||
* Constructor for a phantom cleanable reference.
|
||||
* @param obj the object to monitor
|
||||
* @param cleaner the cleaner
|
||||
* @param action the action Runnable
|
||||
*/
|
||||
public PhantomCleanableRef(Object obj, Cleaner cleaner, Runnable action) {
|
||||
super(obj, cleaner);
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor used only for root of phantom cleanable list.
|
||||
* @param cleanerImpl the cleanerImpl
|
||||
*/
|
||||
PhantomCleanableRef(CleanerImpl cleanerImpl) {
|
||||
super(cleanerImpl);
|
||||
this.action = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performCleanup() {
|
||||
action.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent access to referent even when it is still alive.
|
||||
*
|
||||
* @throws UnsupportedOperationException always
|
||||
*/
|
||||
@Override
|
||||
public Object get() {
|
||||
throw new UnsupportedOperationException("get");
|
||||
}
|
||||
|
||||
/**
|
||||
* Direct clearing of the referent is not supported.
|
||||
*
|
||||
* @throws UnsupportedOperationException always
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException("clear");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform cleaning on an unreachable WeakReference.
|
||||
*/
|
||||
public static final class WeakCleanableRef extends WeakCleanable<Object> {
|
||||
private final Runnable action;
|
||||
|
||||
/**
|
||||
* Constructor for a weak cleanable reference.
|
||||
* @param obj the object to monitor
|
||||
* @param cleaner the cleaner
|
||||
* @param action the action Runnable
|
||||
*/
|
||||
WeakCleanableRef(Object obj, Cleaner cleaner, Runnable action) {
|
||||
super(obj, cleaner);
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor used only for root of weak cleanable list.
|
||||
* @param cleanerImpl the cleanerImpl
|
||||
*/
|
||||
WeakCleanableRef(CleanerImpl cleanerImpl) {
|
||||
super(cleanerImpl);
|
||||
this.action = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performCleanup() {
|
||||
action.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent access to referent even when it is still alive.
|
||||
*
|
||||
* @throws UnsupportedOperationException always
|
||||
*/
|
||||
@Override
|
||||
public Object get() {
|
||||
throw new UnsupportedOperationException("get");
|
||||
}
|
||||
|
||||
/**
|
||||
* Direct clearing of the referent is not supported.
|
||||
*
|
||||
* @throws UnsupportedOperationException always
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException("clear");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform cleaning on an unreachable SoftReference.
|
||||
*/
|
||||
public static final class SoftCleanableRef extends SoftCleanable<Object> {
|
||||
private final Runnable action;
|
||||
|
||||
/**
|
||||
* Constructor for a soft cleanable reference.
|
||||
* @param obj the object to monitor
|
||||
* @param cleaner the cleaner
|
||||
* @param action the action Runnable
|
||||
*/
|
||||
SoftCleanableRef(Object obj, Cleaner cleaner, Runnable action) {
|
||||
super(obj, cleaner);
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor used only for root of soft cleanable list.
|
||||
* @param cleanerImpl the cleanerImpl
|
||||
*/
|
||||
SoftCleanableRef(CleanerImpl cleanerImpl) {
|
||||
super(cleanerImpl);
|
||||
this.action = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performCleanup() {
|
||||
action.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent access to referent even when it is still alive.
|
||||
*
|
||||
* @throws UnsupportedOperationException always
|
||||
*/
|
||||
@Override
|
||||
public Object get() {
|
||||
throw new UnsupportedOperationException("get");
|
||||
}
|
||||
|
||||
/**
|
||||
* Direct clearing of the referent is not supported.
|
||||
*
|
||||
* @throws UnsupportedOperationException always
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException("clear");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A ThreadFactory for InnocuousThreads.
|
||||
* The factory is a singleton.
|
||||
*/
|
||||
static final class InnocuousThreadFactory implements ThreadFactory {
|
||||
final static ThreadFactory factory = new InnocuousThreadFactory();
|
||||
|
||||
static ThreadFactory factory() {
|
||||
return factory;
|
||||
}
|
||||
|
||||
public Thread newThread(Runnable r) {
|
||||
return AccessController.doPrivileged((PrivilegedAction<Thread>) () -> {
|
||||
Thread t = new InnocuousThread(r);
|
||||
t.setPriority(Thread.MAX_PRIORITY - 2);
|
||||
t.setName("Cleaner-" + t.getId());
|
||||
return t;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
735
jdk/test/java/lang/ref/CleanerTest.java
Normal file
735
jdk/test/java/lang/ref/CleanerTest.java
Normal file
@ -0,0 +1,735 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 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.lang.ref.Cleaner;
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.PhantomReference;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Objects;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import jdk.internal.misc.CleanerImpl.PhantomCleanable;
|
||||
import jdk.internal.misc.CleanerImpl.WeakCleanable;
|
||||
import jdk.internal.misc.CleanerImpl.SoftCleanable;
|
||||
|
||||
import org.testng.Assert;
|
||||
import org.testng.TestNG;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @library /lib/testlibrary
|
||||
* @modules java.base/jdk.internal.misc
|
||||
* @run testng/othervm -Xmx4m CleanerTest
|
||||
*/
|
||||
|
||||
@Test
|
||||
public class CleanerTest {
|
||||
// A common CleaningService used by the test for notifications
|
||||
static final Cleaner COMMON = Cleaner.create();
|
||||
|
||||
/**
|
||||
* Test that sequences of the various actions on a Reference
|
||||
* and on the Cleanable instance have the desired result.
|
||||
* The test cases are generated for each of phantom, weak and soft
|
||||
* references.
|
||||
* The sequence of actions includes all permutations to an initial
|
||||
* list of actions including clearing the ref and resulting garbage
|
||||
* collection actions on the reference and explicitly performing
|
||||
* the cleaning action.
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void testCleanableActions() {
|
||||
Cleaner cleaner = Cleaner.create();
|
||||
|
||||
// Individually
|
||||
generateCases(cleaner, c -> c.clearRef());
|
||||
generateCases(cleaner, c -> c.doClean());
|
||||
|
||||
// Pairs
|
||||
generateCases(cleaner, c -> c.doClean(), c -> c.clearRef());
|
||||
|
||||
CleanableCase s = setupPhantom(COMMON, cleaner);
|
||||
cleaner = null;
|
||||
Assert.assertTrue(checkCleaned(s.getSemaphore()),
|
||||
"Cleaner cleanup should have occurred");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the jdk.internal.misc APIs with sequences of the various actions
|
||||
* on a Reference and on the Cleanable instance have the desired result.
|
||||
* The test cases are generated for each of phantom, weak and soft
|
||||
* references.
|
||||
* The sequence of actions includes all permutations to an initial
|
||||
* list of actions including clearing the ref and resulting garbage
|
||||
* collection actions on the reference, explicitly performing
|
||||
* the cleanup and explicitly clearing the cleaning action.
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void testRefSubtypes() {
|
||||
Cleaner cleaner = Cleaner.create();
|
||||
|
||||
// Individually
|
||||
generateCasesInternal(cleaner, c -> c.clearRef());
|
||||
generateCasesInternal(cleaner, c -> c.doClean());
|
||||
generateCasesInternal(cleaner, c -> c.doClear());
|
||||
|
||||
// Pairs
|
||||
generateCasesInternal(cleaner,
|
||||
c -> c.doClear(), c -> c.doClean());
|
||||
|
||||
// Triplets
|
||||
generateCasesInternal(cleaner,
|
||||
c -> c.doClear(), c -> c.doClean(), c -> c.clearRef());
|
||||
|
||||
generateExceptionCasesInternal(cleaner);
|
||||
|
||||
CleanableCase s = setupPhantom(COMMON, cleaner);
|
||||
cleaner = null;
|
||||
Assert.assertTrue(checkCleaned(s.getSemaphore()),
|
||||
"Cleaner cleanup should have occurred");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate tests using the runnables for each of phantom, weak,
|
||||
* and soft references.
|
||||
* @param cleaner the cleaner
|
||||
* @param runnables the sequence of actions on the test case
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
void generateCases(Cleaner cleaner, Consumer<CleanableCase>... runnables) {
|
||||
generateCases(() -> setupPhantom(cleaner, null), runnables.length, runnables);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void generateCasesInternal(Cleaner cleaner, Consumer<CleanableCase>... runnables) {
|
||||
generateCases(() -> setupPhantomSubclass(cleaner, null),
|
||||
runnables.length, runnables);
|
||||
generateCases(() -> setupWeakSubclass(cleaner, null),
|
||||
runnables.length, runnables);
|
||||
generateCases(() -> setupSoftSubclass(cleaner, null),
|
||||
runnables.length, runnables);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void generateExceptionCasesInternal(Cleaner cleaner) {
|
||||
generateCases(() -> setupPhantomSubclassException(cleaner, null),
|
||||
1, c -> c.clearRef());
|
||||
generateCases(() -> setupWeakSubclassException(cleaner, null),
|
||||
1, c -> c.clearRef());
|
||||
generateCases(() -> setupSoftSubclassException(cleaner, null),
|
||||
1, c -> c.clearRef());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate all permutations of the sequence of runnables
|
||||
* and test each one.
|
||||
* The permutations are generated using Heap, B.R. (1963) Permutations by Interchanges.
|
||||
* @param generator the supplier of a CleanableCase
|
||||
* @param n the first index to interchange
|
||||
* @param runnables the sequence of actions
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
void generateCases(Supplier<CleanableCase> generator, int n,
|
||||
Consumer<CleanableCase> ... runnables) {
|
||||
if (n == 1) {
|
||||
CleanableCase test = generator.get();
|
||||
try {
|
||||
verifyGetRef(test);
|
||||
|
||||
// Apply the sequence of actions on the Ref
|
||||
for (Consumer<CleanableCase> c : runnables) {
|
||||
c.accept(test);
|
||||
}
|
||||
verify(test);
|
||||
} catch (Exception e) {
|
||||
Assert.fail(test.toString(), e);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < n - 1; i += 1) {
|
||||
generateCases(generator, n - 1, runnables);
|
||||
Consumer<CleanableCase> t = runnables[n - 1];
|
||||
int ndx = ((n & 1) == 0) ? i : 0;
|
||||
runnables[n - 1] = runnables[ndx];
|
||||
runnables[ndx] = t;
|
||||
}
|
||||
generateCases(generator, n - 1, runnables);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the test case.
|
||||
* Any actions directly on the Reference or Cleanable have been executed.
|
||||
* The CleanableCase under test is given a chance to do the cleanup
|
||||
* by forcing a GC.
|
||||
* The result is compared with the expected result computed
|
||||
* from the sequence of operations on the Cleanable.
|
||||
* The Cleanable itself should have been cleanedup.
|
||||
*
|
||||
* @param test A CleanableCase containing the references
|
||||
*/
|
||||
void verify(CleanableCase test) {
|
||||
System.out.println(test);
|
||||
int r = test.expectedResult();
|
||||
|
||||
CleanableCase cc = setupPhantom(COMMON, test.getCleanable());
|
||||
test.clearCleanable(); // release this hard reference
|
||||
|
||||
boolean result = checkCleaned(test.getSemaphore());
|
||||
if (result) {
|
||||
Assert.assertEquals(r, CleanableCase.EV_CLEAN,
|
||||
"cleaned; but not expected");
|
||||
} else {
|
||||
Assert.assertNotEquals(r, CleanableCase.EV_CLEAN,
|
||||
"not cleaned; expected cleaning");
|
||||
}
|
||||
Assert.assertTrue(checkCleaned(cc.getSemaphore()),
|
||||
"The reference to the Cleanable should have been freed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the reference.get works (or not) as expected.
|
||||
* It handles the cases where UnsupportedOperationException is expected.
|
||||
*
|
||||
* @param test the CleanableCase
|
||||
*/
|
||||
void verifyGetRef(CleanableCase test) {
|
||||
Reference<?> r = (Reference) test.getCleanable();
|
||||
try {
|
||||
Object o = r.get();
|
||||
Reference<?> expectedRef = test.getRef();
|
||||
Assert.assertEquals(expectedRef.get(), o,
|
||||
"Object reference incorrect");
|
||||
if (r.getClass().getName().endsWith("CleanableRef")) {
|
||||
Assert.fail("should not be able to get referent");
|
||||
}
|
||||
} catch (UnsupportedOperationException uoe) {
|
||||
if (r.getClass().getName().endsWith("CleanableRef")) {
|
||||
// Expected exception
|
||||
} else {
|
||||
Assert.fail("Unexpected exception from subclassed cleanable: " +
|
||||
uoe.getMessage() + ", class: " + r.getClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that releasing the reference to the Cleaner service allows it to be
|
||||
* be freed.
|
||||
*/
|
||||
@Test
|
||||
void testCleanerTermination() {
|
||||
ReferenceQueue<Object> queue = new ReferenceQueue<>();
|
||||
Cleaner service = Cleaner.create();
|
||||
|
||||
PhantomReference<Object> ref = new PhantomReference<>(service, queue);
|
||||
System.gc();
|
||||
// Clear the Reference to the cleaning service and force a gc.
|
||||
service = null;
|
||||
System.gc();
|
||||
try {
|
||||
Reference<?> r = queue.remove(1000L);
|
||||
Assert.assertNotNull(r, "queue.remove timeout,");
|
||||
Assert.assertEquals(r, ref, "Wrong Reference dequeued");
|
||||
} catch (InterruptedException ie) {
|
||||
System.out.printf("queue.remove Interrupted%n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a set of semaphores having been released by cleanup handlers.
|
||||
* Force a number of GC cycles to give the GC a chance to process
|
||||
* all the References and for the cleanup actions to be run.
|
||||
*
|
||||
* @param semaphore a varargs list of Semaphores
|
||||
* @return true if all of the semaphores have at least 1 permit,
|
||||
* false otherwise.
|
||||
*/
|
||||
static boolean checkCleaned(Semaphore... semaphore) {
|
||||
long[] cycles = new long[semaphore.length];
|
||||
long total = 0;
|
||||
for (int cycle = 0; cycle < 20; cycle++) {
|
||||
for (int i = 0; i < semaphore.length; i++) {
|
||||
long count = semaphore[i].availablePermits();
|
||||
if (count > 0 && cycles[i] == 0) {
|
||||
System.out.printf(" Cleanable[%d] cleaned in cycle: %d%n", i, cycle);
|
||||
cycles[i] = cycle;
|
||||
total += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (total == semaphore.length) {
|
||||
System.out.printf(" All cleanups done in cycle: %d, total: %d%n",
|
||||
cycle, total);
|
||||
for (int i = 0; i < semaphore.length; i++) {
|
||||
long count = semaphore[i].availablePermits();
|
||||
Assert.assertEquals(count, 1,
|
||||
"Cleanable invoked more than once, semaphore " + i);
|
||||
}
|
||||
return true; // all references freed
|
||||
}
|
||||
// Force GC
|
||||
memoryPressure();
|
||||
}
|
||||
// Not all objects have been cleaned
|
||||
|
||||
for (int i = 0; i < semaphore.length; i++) {
|
||||
if (cycles[i] != 0) {
|
||||
System.out.printf(" Cleanable[%d] cleaned in cycle: %d%n", i, cycles[i]);
|
||||
} else {
|
||||
System.out.printf(" Cleanable[%d] not cleaned%n", i);
|
||||
}
|
||||
}
|
||||
|
||||
return false; // Failing result
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a CleanableCase for a PhantomReference.
|
||||
* @param cleaner the cleaner to use
|
||||
* @param obj an object or null to create a new Object
|
||||
* @return a new CleanableCase preset with the object, cleanup, and semaphore
|
||||
*/
|
||||
static CleanableCase setupPhantom(Cleaner cleaner, Object obj) {
|
||||
if (obj == null) {
|
||||
obj = new Object();
|
||||
}
|
||||
Semaphore s1 = new Semaphore(0);
|
||||
Cleaner.Cleanable c1 = cleaner.register(obj, () -> s1.release());
|
||||
|
||||
return new CleanableCase(new PhantomReference<>(obj, null), c1, s1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a CleanableCase for a PhantomReference.
|
||||
* @param cleaner the cleaner to use
|
||||
* @param obj an object or null to create a new Object
|
||||
* @return a new CleanableCase preset with the object, cleanup, and semaphore
|
||||
*/
|
||||
static CleanableCase setupPhantomSubclass(Cleaner cleaner, Object obj) {
|
||||
if (obj == null) {
|
||||
obj = new Object();
|
||||
}
|
||||
Semaphore s1 = new Semaphore(0);
|
||||
|
||||
Cleaner.Cleanable c1 = new PhantomCleanable<Object>(obj, cleaner) {
|
||||
protected void performCleanup() {
|
||||
s1.release();
|
||||
}
|
||||
};
|
||||
|
||||
return new CleanableCase(new PhantomReference<>(obj, null), c1, s1);
|
||||
}
|
||||
/**
|
||||
* Create a CleanableCase for a WeakReference.
|
||||
* @param cleaner the cleaner to use
|
||||
* @param obj an object or null to create a new Object
|
||||
* @return a new CleanableCase preset with the object, cleanup, and semaphore
|
||||
*/
|
||||
static CleanableCase setupWeakSubclass(Cleaner cleaner, Object obj) {
|
||||
if (obj == null) {
|
||||
obj = new Object();
|
||||
}
|
||||
Semaphore s1 = new Semaphore(0);
|
||||
|
||||
Cleaner.Cleanable c1 = new WeakCleanable<Object>(obj, cleaner) {
|
||||
protected void performCleanup() {
|
||||
s1.release();
|
||||
}
|
||||
};
|
||||
|
||||
return new CleanableCase(new WeakReference<>(obj, null), c1, s1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a CleanableCase for a SoftReference.
|
||||
* @param cleaner the cleaner to use
|
||||
* @param obj an object or null to create a new Object
|
||||
* @return a new CleanableCase preset with the object, cleanup, and semaphore
|
||||
*/
|
||||
static CleanableCase setupSoftSubclass(Cleaner cleaner, Object obj) {
|
||||
if (obj == null) {
|
||||
obj = new Object();
|
||||
}
|
||||
Semaphore s1 = new Semaphore(0);
|
||||
|
||||
Cleaner.Cleanable c1 = new SoftCleanable<Object>(obj, cleaner) {
|
||||
protected void performCleanup() {
|
||||
s1.release();
|
||||
}
|
||||
};
|
||||
|
||||
return new CleanableCase(new SoftReference<>(obj, null), c1, s1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a CleanableCase for a PhantomReference.
|
||||
* @param cleaner the cleaner to use
|
||||
* @param obj an object or null to create a new Object
|
||||
* @return a new CleanableCase preset with the object, cleanup, and semaphore
|
||||
*/
|
||||
static CleanableCase setupPhantomSubclassException(Cleaner cleaner, Object obj) {
|
||||
if (obj == null) {
|
||||
obj = new Object();
|
||||
}
|
||||
Semaphore s1 = new Semaphore(0);
|
||||
|
||||
Cleaner.Cleanable c1 = new PhantomCleanable<Object>(obj, cleaner) {
|
||||
protected void performCleanup() {
|
||||
s1.release();
|
||||
throw new RuntimeException("Exception thrown to cleaner thread");
|
||||
}
|
||||
};
|
||||
|
||||
return new CleanableCase(new PhantomReference<>(obj, null), c1, s1, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a CleanableCase for a WeakReference.
|
||||
* @param cleaner the cleaner to use
|
||||
* @param obj an object or null to create a new Object
|
||||
* @return a new CleanableCase preset with the object, cleanup, and semaphore
|
||||
*/
|
||||
static CleanableCase setupWeakSubclassException(Cleaner cleaner, Object obj) {
|
||||
if (obj == null) {
|
||||
obj = new Object();
|
||||
}
|
||||
Semaphore s1 = new Semaphore(0);
|
||||
|
||||
Cleaner.Cleanable c1 = new WeakCleanable<Object>(obj, cleaner) {
|
||||
protected void performCleanup() {
|
||||
s1.release();
|
||||
throw new RuntimeException("Exception thrown to cleaner thread");
|
||||
}
|
||||
};
|
||||
|
||||
return new CleanableCase(new WeakReference<>(obj, null), c1, s1, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a CleanableCase for a SoftReference.
|
||||
* @param cleaner the cleaner to use
|
||||
* @param obj an object or null to create a new Object
|
||||
* @return a new CleanableCase preset with the object, cleanup, and semaphore
|
||||
*/
|
||||
static CleanableCase setupSoftSubclassException(Cleaner cleaner, Object obj) {
|
||||
if (obj == null) {
|
||||
obj = new Object();
|
||||
}
|
||||
Semaphore s1 = new Semaphore(0);
|
||||
|
||||
Cleaner.Cleanable c1 = new SoftCleanable<Object>(obj, cleaner) {
|
||||
protected void performCleanup() {
|
||||
s1.release();
|
||||
throw new RuntimeException("Exception thrown to cleaner thread");
|
||||
}
|
||||
};
|
||||
|
||||
return new CleanableCase(new SoftReference<>(obj, null), c1, s1, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* MemoryPressure allocates memory to force a gc and to clear SoftReferences.
|
||||
*/
|
||||
static void memoryPressure() {
|
||||
SoftReference<Object> soft = new SoftReference<>(new Object(), null);
|
||||
Vector<Object> root = new Vector<>();
|
||||
try {
|
||||
long free = 0;
|
||||
while (soft.get() != null) {
|
||||
long[] extra = new long[50_000];
|
||||
root.addElement(extra);
|
||||
}
|
||||
} catch (OutOfMemoryError mem) {
|
||||
// ignore
|
||||
root = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CleanableCase encapsulates the objects used for a test.
|
||||
* The reference to the object is not held directly,
|
||||
* but in a Reference object that can be cleared.
|
||||
* The semaphore is used to count whether the cleanup occurred.
|
||||
* It can be awaited on to determine that the cleanup has occurred.
|
||||
* It can be checked for non-zero to determine if it was
|
||||
* invoked or if it was invoked twice (a bug).
|
||||
*/
|
||||
static class CleanableCase {
|
||||
|
||||
private volatile Reference<?> ref;
|
||||
private volatile Cleaner.Cleanable cleanup;
|
||||
private final Semaphore semaphore;
|
||||
private final boolean throwsEx;
|
||||
private final int[] events; // Sequence of calls to clean, clear, etc.
|
||||
private volatile int eventNdx;
|
||||
|
||||
public static int EV_UNKNOWN = 0;
|
||||
public static int EV_CLEAR = 1;
|
||||
public static int EV_CLEAN = 2;
|
||||
public static int EV_UNREF = 3;
|
||||
public static int EV_CLEAR_CLEANUP = 4;
|
||||
|
||||
|
||||
CleanableCase(Reference<Object> ref, Cleaner.Cleanable cleanup,
|
||||
Semaphore semaphore) {
|
||||
this.ref = ref;
|
||||
this.cleanup = cleanup;
|
||||
this.semaphore = semaphore;
|
||||
this.throwsEx = false;
|
||||
this.events = new int[4];
|
||||
this.eventNdx = 0;
|
||||
}
|
||||
CleanableCase(Reference<Object> ref, Cleaner.Cleanable cleanup,
|
||||
Semaphore semaphore,
|
||||
boolean throwsEx) {
|
||||
this.ref = ref;
|
||||
this.cleanup = cleanup;
|
||||
this.semaphore = semaphore;
|
||||
this.throwsEx = throwsEx;
|
||||
this.events = new int[4];
|
||||
this.eventNdx = 0;
|
||||
}
|
||||
|
||||
public Reference<?> getRef() {
|
||||
return ref;
|
||||
}
|
||||
|
||||
public void clearRef() {
|
||||
addEvent(EV_UNREF);
|
||||
ref.clear();
|
||||
}
|
||||
|
||||
public Cleaner.Cleanable getCleanable() {
|
||||
return cleanup;
|
||||
}
|
||||
|
||||
public void doClean() {
|
||||
try {
|
||||
addEvent(EV_CLEAN);
|
||||
cleanup.clean();
|
||||
} catch (RuntimeException ex) {
|
||||
if (!throwsEx) {
|
||||
// unless it is known this case throws an exception, rethrow
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void doClear() {
|
||||
addEvent(EV_CLEAR);
|
||||
((Reference)cleanup).clear();
|
||||
}
|
||||
|
||||
public void clearCleanable() {
|
||||
addEvent(EV_CLEAR_CLEANUP);
|
||||
cleanup = null;
|
||||
}
|
||||
|
||||
public Semaphore getSemaphore() {
|
||||
return semaphore;
|
||||
}
|
||||
|
||||
public boolean isCleaned() {
|
||||
return semaphore.availablePermits() != 0;
|
||||
}
|
||||
|
||||
private synchronized void addEvent(int e) {
|
||||
events[eventNdx++] = e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computed the expected result from the sequence of events.
|
||||
* If EV_CLEAR appears before anything else, it is cleared.
|
||||
* If EV_CLEAN appears before EV_UNREF, then it is cleaned.
|
||||
* Anything else is Unknown.
|
||||
* @return EV_CLEAR if the cleanup should occur;
|
||||
* EV_CLEAN if the cleanup should occur;
|
||||
* EV_UNKNOWN if it is unknown.
|
||||
*/
|
||||
public synchronized int expectedResult() {
|
||||
// Test if EV_CLEAR appears before anything else
|
||||
int clearNdx = indexOfEvent(EV_CLEAR);
|
||||
int cleanNdx = indexOfEvent(EV_CLEAN);
|
||||
int unrefNdx = indexOfEvent(EV_UNREF);
|
||||
if (clearNdx < cleanNdx) {
|
||||
return EV_CLEAR;
|
||||
}
|
||||
if (cleanNdx < clearNdx || cleanNdx < unrefNdx) {
|
||||
return EV_CLEAN;
|
||||
}
|
||||
if (unrefNdx < eventNdx) {
|
||||
return EV_CLEAN;
|
||||
}
|
||||
|
||||
return EV_UNKNOWN;
|
||||
}
|
||||
|
||||
private synchronized int indexOfEvent(int e) {
|
||||
for (int i = 0; i < eventNdx; i++) {
|
||||
if (events[i] == e) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return eventNdx;
|
||||
}
|
||||
|
||||
private static final String[] names =
|
||||
{"UNKNOWN", "EV_CLEAR", "EV_CLEAN", "EV_UNREF", "EV_CLEAR_CLEANUP"};
|
||||
|
||||
public String eventName(int event) {
|
||||
return names[event];
|
||||
}
|
||||
|
||||
public synchronized String eventsString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append('[');
|
||||
for (int i = 0; i < eventNdx; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append(eventName(events[i]));
|
||||
}
|
||||
sb.append(']');
|
||||
sb.append(", throwEx: ");
|
||||
sb.append(throwsEx);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("Case: %s, expect: %s, events: %s",
|
||||
getRef().getClass().getName(),
|
||||
eventName(expectedResult()), eventsString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Example using a Cleaner to remove WeakKey references from a Map.
|
||||
*/
|
||||
@Test
|
||||
void testWeakKey() {
|
||||
ConcurrentHashMap<WeakKey<String>, String> map = new ConcurrentHashMap<>();
|
||||
Cleaner cleaner = Cleaner.create();
|
||||
String key = new String("foo"); // ensure it is not interned
|
||||
String data = "bar";
|
||||
|
||||
map.put(new WeakKey<>(key, cleaner, map), data);
|
||||
|
||||
WeakKey<String> k2 = new WeakKey<>(key, cleaner, map);
|
||||
|
||||
Assert.assertEquals(map.get(k2), data, "value should be found in the map");
|
||||
key = null;
|
||||
System.gc();
|
||||
Assert.assertNotEquals(map.get(k2), data, "value should not be found in the map");
|
||||
|
||||
final int CYCLE_MAX = 30;
|
||||
for (int i = 1; map.size() > 0 && i < CYCLE_MAX; i++) {
|
||||
map.forEach( (k, v) -> System.out.printf(" k: %s, v: %s%n", k, v));
|
||||
try {
|
||||
Thread.sleep(10L);
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
Assert.assertEquals(map.size(), 0, "Expected map to be empty;");
|
||||
cleaner = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test sample class for WeakKeys in Map.
|
||||
* @param <K> A WeakKey of type K
|
||||
*/
|
||||
class WeakKey<K> extends WeakReference<K> {
|
||||
private final int hash;
|
||||
private final ConcurrentHashMap<WeakKey<K>, ?> map;
|
||||
Cleaner.Cleanable cleanable;
|
||||
|
||||
public WeakKey(K key, Cleaner c, ConcurrentHashMap<WeakKey<K>, ?> map) {
|
||||
super(key);
|
||||
this.hash = key.hashCode();
|
||||
this.map = map;
|
||||
cleanable = new WeakCleanable<Object>(key, c) {
|
||||
protected void performCleanup() {
|
||||
map.remove(WeakKey.this);
|
||||
}
|
||||
};
|
||||
}
|
||||
public int hashCode() { return hash; }
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof WeakKey)) return false;
|
||||
K key = get();
|
||||
if (key == null) return obj == this;
|
||||
return key == ((WeakKey<?>)obj).get();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "WeakKey:" + Objects.toString(get() + ", cleanableRef: " +
|
||||
((Reference)cleanable).get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that casting a Cleanup to a Reference is not allowed to
|
||||
* get the referent or clear the reference.
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
void testReferentNotAvailable() {
|
||||
Cleaner cleaner = Cleaner.create();
|
||||
Semaphore s1 = new Semaphore(0);
|
||||
|
||||
Object obj = new String("a new string");
|
||||
Cleaner.Cleanable c = cleaner.register(obj, () -> s1.release());
|
||||
Reference r = (Reference) c;
|
||||
try {
|
||||
Object o = r.get();
|
||||
System.out.printf("r: %s%n", Objects.toString(o));
|
||||
Assert.fail("should not be able to get the referent from Cleanable");
|
||||
} catch (UnsupportedOperationException uoe) {
|
||||
// expected
|
||||
}
|
||||
|
||||
try {
|
||||
r.clear();
|
||||
Assert.fail("should not be able to clear the referent from Cleanable");
|
||||
} catch (UnsupportedOperationException uoe) {
|
||||
// expected
|
||||
}
|
||||
|
||||
obj = null;
|
||||
Assert.assertTrue(checkCleaned(s1), "reference should be cleaned;");
|
||||
cleaner = null;
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user