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:
Roger Riggs 2015-12-21 11:34:14 -05:00
parent 72bed9c5df
commit 197207b152
4 changed files with 1759 additions and 1 deletions

View 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();
}
}

View File

@ -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.

View File

@ -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;
});
}
}
}

View 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;
}
}