< prev index next >
test/java/lang/ref/CleanerTest.java
Print this page
@@ -19,36 +19,34 @@
* 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 jdk.internal.ref.CleanerFactory;
+import jdk.internal.ref.CleanerImpl;
+import jdk.internal.ref.PhantomCleanable;
+import jdk.internal.ref.SoftCleanable;
+import jdk.internal.ref.WeakCleanable;
+import jdk.test.lib.Utils;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import sun.hotspot.WhiteBox;
+
import java.lang.ref.Cleaner;
-import java.lang.ref.Reference;
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.util.Arrays;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Supplier;
-import jdk.internal.ref.PhantomCleanable;
-import jdk.internal.ref.WeakCleanable;
-import jdk.internal.ref.SoftCleanable;
-import jdk.internal.ref.CleanerFactory;
-
-import sun.hotspot.WhiteBox;
-
-import jdk.test.lib.Utils;
-
-import org.testng.Assert;
-import org.testng.TestNG;
-import org.testng.annotations.Test;
-
/*
* @test
* @library /test/lib/share/classes /lib/testlibrary /test/lib
* @build sun.hotspot.WhiteBox
* @build jdk.test.lib.Utils
@@ -83,20 +81,25 @@
@Test
@SuppressWarnings("unchecked")
void testCleanableActions() {
Cleaner cleaner = Cleaner.create();
+ // No actions
+ generateCases(cleaner);
+
// Individually
- generateCases(cleaner, c -> c.clearRef());
- generateCases(cleaner, c -> c.doClean());
+ generateCases(cleaner, CleanableCase::doClean);
+ generateCases(cleaner, CleanableCase::releaseReferent);
// Pairs
- generateCases(cleaner, c -> c.doClean(), c -> c.clearRef());
+ generateCases(cleaner,
+ CleanableCase::doClean,
+ CleanableCase::releaseReferent);
- CleanableCase s = setupPhantom(COMMON, cleaner);
+ CleanableCase s = setupPhantom(COMMON, cleaner).releaseReferent();
cleaner = null;
- checkCleaned(s.getSemaphore(), true, "Cleaner was cleaned:");
+ checkCleaned(s.getSemaphore(), 1, "Cleaner was cleaned:");
}
/**
* Test the jdk.internal.misc APIs with sequences of the various actions
* on a Reference and on the Cleanable instance have the desired result.
@@ -110,28 +113,42 @@
@Test
@SuppressWarnings("unchecked")
void testRefSubtypes() {
Cleaner cleaner = Cleaner.create();
+ // No acions
+ generateCasesInternal(cleaner);
+
// Individually
- generateCasesInternal(cleaner, c -> c.clearRef());
- generateCasesInternal(cleaner, c -> c.doClean());
- generateCasesInternal(cleaner, c -> c.doClear());
+ generateCasesInternal(cleaner, CleanableCase::doClear);
+ generateCasesInternal(cleaner, CleanableCase::doClean);
+ generateCasesInternal(cleaner, CleanableCase::releaseReferent);
// Pairs
generateCasesInternal(cleaner,
- c -> c.doClear(), c -> c.doClean());
+ CleanableCase::doClear,
+ CleanableCase::doClean);
+
+ generateCasesInternal(cleaner,
+ CleanableCase::doClear,
+ CleanableCase::releaseReferent);
+
+ generateCasesInternal(cleaner,
+ CleanableCase::doClean,
+ CleanableCase::releaseReferent);
// Triplets
generateCasesInternal(cleaner,
- c -> c.doClear(), c -> c.doClean(), c -> c.clearRef());
+ CleanableCase::doClear,
+ CleanableCase::doClean,
+ CleanableCase::releaseReferent);
generateExceptionCasesInternal(cleaner);
- CleanableCase s = setupPhantom(COMMON, cleaner);
+ CleanableCase s = setupPhantom(COMMON, cleaner).releaseReferent();
cleaner = null;
- checkCleaned(s.getSemaphore(), true, "Cleaner was cleaned:");
+ checkCleaned(s.getSemaphore(), 1, "Cleaner was cleaned:");
}
/**
* Generate tests using the runnables for each of phantom, weak,
* and soft references.
@@ -154,15 +171,15 @@
}
@SuppressWarnings("unchecked")
void generateExceptionCasesInternal(Cleaner cleaner) {
generateCases(() -> setupPhantomSubclassException(cleaner, null),
- 1, c -> c.clearRef());
+ 1, CleanableCase::releaseReferent);
generateCases(() -> setupWeakSubclassException(cleaner, null),
- 1, c -> c.clearRef());
+ 1, CleanableCase::releaseReferent);
generateCases(() -> setupSoftSubclassException(cleaner, null),
- 1, c -> c.clearRef());
+ 1, CleanableCase::releaseReferent);
}
/**
* Generate all permutations of the sequence of runnables
* and test each one.
@@ -172,11 +189,11 @@
* @param runnables the sequence of actions
*/
@SuppressWarnings("unchecked")
void generateCases(Supplier<CleanableCase> generator, int n,
Consumer<CleanableCase> ... runnables) {
- if (n == 1) {
+ if (n <= 1) {
CleanableCase test = generator.get();
try {
verifyGetRef(test);
// Apply the sequence of actions on the Ref
@@ -209,20 +226,30 @@
* The Cleanable itself should have been cleanedup.
*
* @param test A CleanableCase containing the references
*/
void verify(CleanableCase test) {
+
+ CleanableCase cc = setupPhantom(COMMON, test.getCleanable()).releaseReferent();
+ // release Cleanable if impl. class
+ test.releaseCleanableIfImpl();
+
System.out.println(test);
- int r = test.expectedResult();
+ int expectedCleanups = test.expectedCleanups();
+
+ checkCleaned(test.getSemaphore(), expectedCleanups,
+ test.cleanableDescr + " was cleaned:");
+
+ // release Cleanable unconditionally
+ test.releaseCleanable();
- CleanableCase cc = setupPhantom(COMMON, test.getCleanable());
- test.clearCleanable(); // release this hard reference
+ // only if the Cleanable is a CleanerImpl.XxxCleanableImpl class and
+ // it was not cleaned it will remain reachable...
+ int expectedCleanableCleanups =
+ (test.isCleanableImplClass && expectedCleanups == 0) ? 0 : 1;
- checkCleaned(test.getSemaphore(),
- r == CleanableCase.EV_CLEAN,
- "Cleanable was cleaned:");
- checkCleaned(cc.getSemaphore(), true,
+ checkCleaned(cc.getSemaphore(), expectedCleanableCleanups,
"The reference to the Cleanable was freed:");
}
/**
* Verify that the reference.get works (or not) as expected.
@@ -232,27 +259,38 @@
*/
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")) {
+ if (isCleanableImplClass(r.getClass())) {
Assert.fail("should not be able to get referent");
}
+ Object expected = r instanceof PhantomReference
+ ? null
+ : test.getReferent();
+ Assert.assertEquals(expected, o,
+ "Object reference incorrect");
} catch (UnsupportedOperationException uoe) {
- if (r.getClass().getName().endsWith("CleanableRef")) {
+ if (isCleanableImplClass(r.getClass())) {
// Expected exception
} else {
Assert.fail("Unexpected exception from subclassed cleanable: " +
uoe.getMessage() + ", class: " + r.getClass());
}
}
}
/**
+ * @return true if given clazz is one of the CleanerImpl.XxxCleanableImpl
+ * classes
+ */
+ static boolean isCleanableImplClass(Class<?> clazz) {
+ return Cleaner.Cleanable.class.isAssignableFrom(clazz) &&
+ clazz.getEnclosingClass() == CleanerImpl.class;
+ }
+
+ /**
* Test that releasing the reference to the Cleaner service allows it to be
* be freed.
*/
@Test
void testCleanerTermination() {
@@ -272,39 +310,66 @@
System.out.printf("queue.remove Interrupted%n");
}
}
/**
- * Check a semaphore having been released by cleanup handler.
+ * Check a semaphore having been released by cleanup handler for the number of times.
* Force a number of GC cycles to give the GC a chance to process
* the Reference and for the cleanup action to be run.
* Use a larger number of cycles to wait for an expected cleaning to occur.
*
* @param semaphore a Semaphore
- * @param expectCleaned true if cleaning should occur
+ * @param expectedCleanups # of expected cleanups to occur
* @param msg a message to explain the error
*/
- static void checkCleaned(Semaphore semaphore, boolean expectCleaned,
+ static void checkCleaned(Semaphore semaphore, int expectedCleanups,
String msg) {
- long max_cycles = expectCleaned ? 10 : 3;
+ if (expectedCleanups < 0) {
+ // can't predict - anything is OK
+ whitebox.fullGC();
+ int acquired = drain(semaphore);
+ System.out.printf(msg + " %d times\n", acquired);
+ return;
+ }
+
+ long max_cycles = expectedCleanups > 0 ? 10 : 3;
+ // wait for at least 1 permit
+ int permits = Math.max(1, expectedCleanups);
long cycle = 0;
for (; cycle < max_cycles; cycle++) {
// Force GC
whitebox.fullGC();
try {
- if (semaphore.tryAcquire(Utils.adjustTimeout(10L), TimeUnit.MILLISECONDS)) {
- System.out.printf(" Cleanable cleaned in cycle: %d%n", cycle);
- Assert.assertEquals(true, expectCleaned, msg);
+ if (semaphore.tryAcquire(permits,
+ Utils.adjustTimeout(10L), TimeUnit.MILLISECONDS)) {
+ int acquired = drain(semaphore) + permits;
+ System.out.printf(msg + " %d times in cycle %d\n", acquired, cycle);
+ Assert.assertEquals(acquired, expectedCleanups, msg);
return;
}
} catch (InterruptedException ie) {
// retry in outer loop
}
}
- // Object has not been cleaned
- Assert.assertEquals(false, expectCleaned, msg);
+ // cleanup has not been invoked for at least 'permits' # of times
+ int acquired = drain(semaphore);
+ System.out.printf(msg + " %d times\n", acquired);
+ Assert.assertEquals(acquired, expectedCleanups, msg);
+ }
+
+ // like semaphore.drain() but wait a while for a permit to be available
+ private static int drain(Semaphore semaphore) {
+ int acquired = 0;
+ try {
+ while (semaphore.tryAcquire(Utils.adjustTimeout(10L), TimeUnit.MILLISECONDS)) {
+ acquired++;
+ }
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ return acquired;
}
/**
* Create a CleanableCase for a PhantomReference.
* @param cleaner the cleaner to use
@@ -314,13 +379,13 @@
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());
+ Cleaner.Cleanable c1 = cleaner.register(obj, s1::release);
- return new CleanableCase(new PhantomReference<>(obj, null), c1, s1);
+ return new CleanableCase(obj, c1, s1);
}
/**
* Create a CleanableCase for a PhantomReference.
* @param cleaner the cleaner to use
@@ -331,17 +396,17 @@
if (obj == null) {
obj = new Object();
}
Semaphore s1 = new Semaphore(0);
- Cleaner.Cleanable c1 = new PhantomCleanable<Object>(obj, cleaner) {
- protected void performCleanup() {
+ Cleaner.Cleanable c1 = new PhantomCleanable<>(obj, cleaner) {
+ public void clean() {
s1.release();
}
};
- return new CleanableCase(new PhantomReference<>(obj, null), c1, s1);
+ return new CleanableCase(obj, 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
@@ -351,17 +416,17 @@
if (obj == null) {
obj = new Object();
}
Semaphore s1 = new Semaphore(0);
- Cleaner.Cleanable c1 = new WeakCleanable<Object>(obj, cleaner) {
- protected void performCleanup() {
+ Cleaner.Cleanable c1 = new WeakCleanable<>(obj, cleaner) {
+ public void clean() {
s1.release();
}
};
- return new CleanableCase(new WeakReference<>(obj, null), c1, s1);
+ return new CleanableCase(obj, c1, s1);
}
/**
* Create a CleanableCase for a SoftReference.
* @param cleaner the cleaner to use
@@ -372,17 +437,17 @@
if (obj == null) {
obj = new Object();
}
Semaphore s1 = new Semaphore(0);
- Cleaner.Cleanable c1 = new SoftCleanable<Object>(obj, cleaner) {
- protected void performCleanup() {
+ Cleaner.Cleanable c1 = new SoftCleanable<>(obj, cleaner) {
+ public void clean() {
s1.release();
}
};
- return new CleanableCase(new SoftReference<>(obj, null), c1, s1);
+ return new CleanableCase(obj, c1, s1);
}
/**
* Create a CleanableCase for a PhantomReference.
* @param cleaner the cleaner to use
@@ -393,18 +458,18 @@
if (obj == null) {
obj = new Object();
}
Semaphore s1 = new Semaphore(0);
- Cleaner.Cleanable c1 = new PhantomCleanable<Object>(obj, cleaner) {
- protected void performCleanup() {
+ Cleaner.Cleanable c1 = new PhantomCleanable<>(obj, cleaner) {
+ public void clean() {
s1.release();
throw new RuntimeException("Exception thrown to cleaner thread");
}
};
- return new CleanableCase(new PhantomReference<>(obj, null), c1, s1, true);
+ return new CleanableCase(obj, c1, s1, true);
}
/**
* Create a CleanableCase for a WeakReference.
* @param cleaner the cleaner to use
@@ -415,18 +480,18 @@
if (obj == null) {
obj = new Object();
}
Semaphore s1 = new Semaphore(0);
- Cleaner.Cleanable c1 = new WeakCleanable<Object>(obj, cleaner) {
- protected void performCleanup() {
+ Cleaner.Cleanable c1 = new WeakCleanable<>(obj, cleaner) {
+ public void clean() {
s1.release();
throw new RuntimeException("Exception thrown to cleaner thread");
}
};
- return new CleanableCase(new WeakReference<>(obj, null), c1, s1, true);
+ return new CleanableCase(obj, c1, s1, true);
}
/**
* Create a CleanableCase for a SoftReference.
* @param cleaner the cleaner to use
@@ -437,18 +502,18 @@
if (obj == null) {
obj = new Object();
}
Semaphore s1 = new Semaphore(0);
- Cleaner.Cleanable c1 = new SoftCleanable<Object>(obj, cleaner) {
- protected void performCleanup() {
+ Cleaner.Cleanable c1 = new SoftCleanable<>(obj, cleaner) {
+ public void clean() {
s1.release();
throw new RuntimeException("Exception thrown to cleaner thread");
}
};
- return new CleanableCase(new SoftReference<>(obj, null), c1, s1, true);
+ return new CleanableCase(obj, c1, s1, true);
}
/**
* CleanableCase encapsulates the objects used for a test.
* The reference to the object is not held directly,
@@ -458,153 +523,182 @@
* 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 volatile Object referent;
+ private volatile Cleaner.Cleanable cleanable;
private final Semaphore semaphore;
private final boolean throwsEx;
- private final int[] events; // Sequence of calls to clean, clear, etc.
+ final String cleanableDescr;
+ final boolean isCleanableImplClass;
+ private final Event[] 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;
+ enum Event {
+ UNKNOWN, CLEAR, CLEAN, RELEASE_REFERENT, RELEASE_CLEANABLE;
+
+ boolean isBeforeIn(Event e, Event[] events) {
+ return this.indexIn(events) < e.indexIn(events);
+ }
+
+ boolean isPresentIn(Event[] events) {
+ return this.indexIn(events) < events.length;
+ }
+ int countOccurrencesIn(Event[] events) {
+ int count = 0;
+ for (int i = 0; i < events.length && events[i] != null; i++) {
+ if (events[i] == this) {
+ count++;
+ }
+ }
+ return count;
+ }
- CleanableCase(Reference<Object> ref, Cleaner.Cleanable cleanup,
+ private int indexIn(Event[] events) {
+ for (int i = 0; i < events.length && events[i] != null; i++) {
+ if (events[i] == this) {
+ return i;
+ }
+ }
+ return events.length;
+ }
+ }
+
+ CleanableCase(Object referent, Cleaner.Cleanable cleanable,
Semaphore semaphore) {
- this.ref = ref;
- this.cleanup = cleanup;
- this.semaphore = semaphore;
- this.throwsEx = false;
- this.events = new int[4];
- this.eventNdx = 0;
+ this(referent, cleanable, semaphore, false);
}
- CleanableCase(Reference<Object> ref, Cleaner.Cleanable cleanup,
+ CleanableCase(Object referent, Cleaner.Cleanable cleanable,
Semaphore semaphore,
boolean throwsEx) {
- this.ref = ref;
- this.cleanup = cleanup;
+ this.referent = referent;
+ this.cleanable = cleanable;
this.semaphore = semaphore;
this.throwsEx = throwsEx;
- this.events = new int[4];
+ this.isCleanableImplClass = isCleanableImplClass(cleanable.getClass());
+ this.cleanableDescr = cleanable.getClass().isAnonymousClass()
+ ? cleanable.getClass().getSuperclass().getName()
+ : cleanable.getClass().getName();
+ this.events = new Event[5];
this.eventNdx = 0;
}
- public Reference<?> getRef() {
- return ref;
+ public Object getReferent() {
+ return referent;
}
- public void clearRef() {
- addEvent(EV_UNREF);
- ref.clear();
+ public CleanableCase releaseReferent() {
+ if (referent != null) {
+ addEvent(Event.RELEASE_REFERENT);
+ referent = null;
+ }
+ return this;
}
public Cleaner.Cleanable getCleanable() {
- return cleanup;
+ return cleanable;
}
public void doClean() {
try {
- addEvent(EV_CLEAN);
- cleanup.clean();
+ addEvent(Event.CLEAN);
+ cleanable.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();
+ addEvent(Event.CLEAR);
+ ((Reference) cleanable).clear();
+ }
+
+ public void releaseCleanableIfImpl() {
+ if (isCleanableImplClass) {
+ // only the CleanerImpl.XxxCleanableImpl instances are guaranteed
+ // to be retained by CleanerImpl so we can only release them and
+ // still expect the Cleanable to be cleaned...
+ releaseCleanable();
+ }
}
- public void clearCleanable() {
- addEvent(EV_CLEAR_CLEANUP);
- cleanup = null;
+ public void releaseCleanable() {
+ if (cleanable != null) {
+ // unconditionally release any Cleanable
+ addEvent(Event.RELEASE_CLEANABLE);
+ cleanable = null;
+ }
}
public Semaphore getSemaphore() {
return semaphore;
}
public boolean isCleaned() {
return semaphore.availablePermits() != 0;
}
- private synchronized void addEvent(int e) {
+ private synchronized void addEvent(Event 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;
- }
+ * Compute the number of expected cleanups to be triggered
+ * given the type of Cleanable implementation and the collected events.
+ * Return -1 if we can't predict the number.
+ */
+ public synchronized int expectedCleanups() {
+
+ if (isCleanableImplClass) { // the CleanerImpl.XxxCleanableImpl class
+
+ // doesn't matter how many or what the order of events was as long
+ // as either the clean() method was called explicitly or
+ // the referent was released, we expect exactly one cleanup action
+ // otherwise none
+ return Event.CLEAN.isPresentIn(events) ||
+ Event.RELEASE_REFERENT.isPresentIn(events) ? 1 : 0;
+
+ } else { // plain XxxCleanable subclass
+
+ // each time clean() is invoked explicitly, a cleanup action is
+ // performed (no at-most-once semantics here)
+ int explicitCleanups = Event.CLEAN.countOccurrencesIn(events);
+
+ // if we clear()-ed the referent before releasing it,
+ // then that's all we get
+ if (Event.CLEAR.isBeforeIn(Event.RELEASE_REFERENT, events)) {
+ return explicitCleanups;
+ }
+
+ // else only if Cleanable was not released and clear() was not
+ // called, can we expect any predictable behavior from plain
+ // XxxCleanable subclass...
+ if (!Event.RELEASE_CLEANABLE.isPresentIn(events) &&
+ !Event.CLEAR.isPresentIn(events)) {
+ // we get one additional cleanup to all the explicit ones
+ // if referent was released...
+ return explicitCleanups +
+ (Event.RELEASE_REFERENT.isPresentIn(events)? 1 : 0);
}
- 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];
+ // -1 means we can't predict
+ return -1;
+ }
}
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();
+ return Arrays.asList(Arrays.copyOf(events, eventNdx)).toString();
}
public String toString() {
- return String.format("Case: %s, expect: %s, events: %s",
- getRef().getClass().getName(),
- eventName(expectedResult()), eventsString());
+ return String.format("Case: %s, expected cleanups: %s, events: %s",
+ cleanableDescr, expectedCleanups(), eventsString());
}
}
/**
@@ -615,15 +709,19 @@
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> k1 = new WeakKey<>(key, cleaner, map);
+ map.put(k1, data);
WeakKey<String> k2 = new WeakKey<>(key, cleaner, map);
Assert.assertEquals(map.get(k2), data, "value should be found in the map");
+ // ensure key is reachable until the lookup above
+ Reference.reachabilityFence(key);
+ // make key unreachable now
key = null;
System.gc();
Assert.assertNotEquals(map.get(k2), data, "value should not be found in the map");
final long CYCLE_MAX = Utils.adjustTimeout(30L);
@@ -632,47 +730,47 @@
try {
Thread.sleep(10L);
} catch (InterruptedException ie) {}
}
Assert.assertEquals(map.size(), 0, "Expected map to be empty;");
- cleaner = null;
+ // ensure cleaner an k1 remain reachable until the end of the test
+ Reference.reachabilityFence(cleaner);
+ Reference.reachabilityFence(k1);
}
/**
* Test sample class for WeakKeys in Map.
* @param <K> A WeakKey of type K
*/
- class WeakKey<K> extends WeakReference<K> {
+ class WeakKey<K> extends WeakCleanable<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);
+ super(key, c);
this.hash = key.hashCode();
this.map = map;
- cleanable = new WeakCleanable<Object>(key, c) {
- protected void performCleanup() {
- map.remove(WeakKey.this);
}
- };
+
+ @Override
+ public void clean() {
+ map.remove(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();
+ return key != null && key == ((WeakKey<?>)obj).get();
}
public String toString() {
- return "WeakKey:" + Objects.toString(get() + ", cleanableRef: " +
- ((Reference)cleanable).get());
+ return "WeakKey:" + get();
}
}
/**
* Verify that casting a Cleanup to a Reference is not allowed to
@@ -700,24 +798,24 @@
Assert.fail("should not be able to clear the referent from Cleanable");
} catch (UnsupportedOperationException uoe) {
// expected
}
+ // ensure obj is reachable until now
+ Reference.reachabilityFence(obj);
+ // make it unreachable now
obj = null;
- checkCleaned(s1, true, "reference was cleaned:");
- cleaner = null;
+ checkCleaned(s1, 1, "reference was cleaned:");
}
/**
* Test the Cleaner from the CleanerFactory.
*/
@Test
void testCleanerFactory() {
Cleaner cleaner = CleanerFactory.cleaner();
- Object obj = new Object();
- CleanableCase s = setupPhantom(cleaner, obj);
- obj = null;
- checkCleaned(s.getSemaphore(), true,
+ CleanableCase s = setupPhantom(cleaner, null).releaseReferent();
+ checkCleaned(s.getSemaphore(), 1,
"Object was cleaned using CleanerFactor.cleaner():");
}
}
< prev index next >