8256517: (ref) Reference.clear during reference processing may lose notification

8240696: (ref) Reference.clear may extend the lifetime of the referent

Use private native helper to implement Reference.clear.

Reviewed-by: pliden, rkennke, mchung
This commit is contained in:
Kim Barrett 2020-11-25 03:34:50 +00:00
parent 3c230b8ac5
commit 66943fefa7
14 changed files with 295 additions and 31 deletions

View File

@ -177,6 +177,7 @@ JVM_RawMonitorCreate
JVM_RawMonitorDestroy
JVM_RawMonitorEnter
JVM_RawMonitorExit
JVM_ReferenceClear
JVM_ReferenceRefersTo
JVM_RegisterLambdaProxyClassForArchiving
JVM_RegisterSignal

View File

@ -57,3 +57,7 @@ void ZBreakpoint::at_after_marking_started() {
void ZBreakpoint::at_before_marking_completed() {
ConcurrentGCBreakpoints::at("BEFORE MARKING COMPLETED");
}
void ZBreakpoint::at_after_reference_processing_started() {
ConcurrentGCBreakpoints::at("AFTER CONCURRENT REFERENCE PROCESSING STARTED");
}

View File

@ -37,6 +37,7 @@ public:
static void at_after_gc();
static void at_after_marking_started();
static void at_before_marking_completed();
static void at_after_reference_processing_started();
};
#endif // SHARE_GC_Z_ZBREAKPOINT_HPP

View File

@ -322,6 +322,7 @@ void ZDriver::concurrent_mark_continue() {
void ZDriver::concurrent_process_non_strong_references() {
ZStatTimer timer(ZPhaseConcurrentProcessNonStrongReferences);
ZBreakpoint::at_after_reference_processing_started();
ZHeap::heap()->process_non_strong_references();
}

View File

@ -183,12 +183,6 @@ bool ZReferenceProcessor::should_discover(oop reference, ReferenceType type) con
}
bool ZReferenceProcessor::should_drop(oop reference, ReferenceType type) const {
// This check is racing with a call to Reference.clear() from the application.
// If the application clears the reference after this check it will still end
// up on the pending list, and there's nothing we can do about that without
// changing the Reference.clear() API. This check is also racing with a call
// to Reference.enqueue() from the application, which is unproblematic, since
// the application wants the reference to be enqueued anyway.
const oop referent = reference_referent(reference);
if (referent == NULL) {
// Reference has been cleared, by a call to Reference.enqueue()

View File

@ -333,6 +333,9 @@ JVM_WaitForReferencePendingList(JNIEnv *env);
JNIEXPORT jboolean JNICALL
JVM_ReferenceRefersTo(JNIEnv *env, jobject ref, jobject o);
JNIEXPORT void JNICALL
JVM_ReferenceClear(JNIEnv *env, jobject ref);
/*
* java.lang.ref.PhantomReference
*/

View File

@ -3446,6 +3446,25 @@ JVM_ENTRY(jboolean, JVM_ReferenceRefersTo(JNIEnv* env, jobject ref, jobject o))
return referent == JNIHandles::resolve(o);
JVM_END
JVM_ENTRY(void, JVM_ReferenceClear(JNIEnv* env, jobject ref))
JVMWrapper("JVM_ReferenceClear");
oop ref_oop = JNIHandles::resolve_non_null(ref);
// FinalReference has it's own implementation of clear().
assert(!java_lang_ref_Reference::is_final(ref_oop), "precondition");
if (java_lang_ref_Reference::unknown_referent_no_keepalive(ref_oop) == NULL) {
// If the referent has already been cleared then done.
// However, if the referent is dead but has not yet been cleared by
// concurrent reference processing, it should NOT be cleared here.
// Instead, clearing should be left to the GC. Clearing it here could
// detectably lose an expected notification, which is impossible with
// STW reference processing. The clearing in enqueue() doesn't have
// this problem, since the enqueue covers the notification, but it's not
// worth the effort to handle that case specially.
return;
}
java_lang_ref_Reference::clear_referent(ref_oop);
JVM_END
// java.lang.ref.PhantomReference //////////////////////////////////////////////////

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2020, 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
@ -34,6 +34,22 @@ class FinalReference<T> extends Reference<T> {
super(referent, q);
}
/* May only be called when the reference is inactive, so no longer weak. */
@Override
public T get() {
// Cannot call super.get() when active, as the GC could
// deactivate immediately after the test.
return getFromInactiveFinalReference();
}
/* May only be called when the reference is inactive, so no longer weak.
* Clearing while active would discard the finalization request.
*/
@Override
public void clear() {
clearInactiveFinalReference();
}
@Override
public boolean enqueue() {
throw new InternalError("should never reach here");

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -82,7 +82,7 @@ final class Finalizer extends FinalReference<Object> { /* Package-private; must
}
try {
Object finalizee = this.getInactive();
Object finalizee = this.get();
assert finalizee != null;
if (!(finalizee instanceof java.lang.Enum)) {
jla.invokeFinalize(finalizee);

View File

@ -100,10 +100,10 @@ public abstract class Reference<T> {
* [active/unregistered] [1]
*
* Transitions:
* clear
* clear [2]
* [active/registered] -------> [inactive/registered]
* | |
* | | enqueue [2]
* | | enqueue
* | GC enqueue [2] |
* | -----------------|
* | |
@ -114,7 +114,7 @@ public abstract class Reference<T> {
* v | |
* [pending/enqueued] --- |
* | | poll/remove
* | poll/remove |
* | poll/remove | + clear [4]
* | |
* v ReferenceHandler v
* [pending/dequeued] ------> [inactive/dequeued]
@ -140,12 +140,14 @@ public abstract class Reference<T> {
* [1] Unregistered is not permitted for FinalReferences.
*
* [2] These transitions are not possible for FinalReferences, making
* [pending/enqueued] and [pending/dequeued] unreachable, and
* [inactive/registered] terminal.
* [pending/enqueued], [pending/dequeued], and [inactive/registered]
* unreachable.
*
* [3] The garbage collector may directly transition a Reference
* from [active/unregistered] to [inactive/unregistered],
* bypassing the pending-Reference list.
*
* [4] The queue handler for FinalReferences also clears the reference.
*/
private T referent; /* Treated specially by GC */
@ -342,22 +344,6 @@ public abstract class Reference<T> {
return this.referent;
}
/**
* Load referent with strong semantics. Treating the referent
* as strong referent is ok when the Reference is inactive,
* because then the referent is switched to strong semantics
* anyway.
*
* This is only used from Finalizer to bypass the intrinsic,
* which might return a null referent, even though it is not
* null, and would subsequently not finalize the referent/finalizee.
*/
T getInactive() {
assert this instanceof FinalReference;
assert next == this; // I.e. FinalReference is inactive
return this.referent;
}
/**
* Tests if the referent of this reference object is {@code obj}.
* Using a {@code null} {@code obj} returns {@code true} if the
@ -383,6 +369,41 @@ public abstract class Reference<T> {
* clears references it does so directly, without invoking this method.
*/
public void clear() {
clear0();
}
/* Implementation of clear(), also used by enqueue(). A simple
* assignment of the referent field won't do for some garbage
* collectors.
*/
private native void clear0();
/* -- Operations on inactive FinalReferences -- */
/* These functions are only used by FinalReference, and must only be
* called after the reference becomes inactive. While active, a
* FinalReference is considered weak but the referent is not normally
* accessed. Once a FinalReference becomes inactive it is considered a
* strong reference. These functions are used to bypass the
* corresponding weak implementations, directly accessing the referent
* field with strong semantics.
*/
/**
* Load referent with strong semantics.
*/
T getFromInactiveFinalReference() {
assert this instanceof FinalReference;
assert next != null; // I.e. FinalReference is inactive
return this.referent;
}
/**
* Clear referent with strong semantics.
*/
void clearInactiveFinalReference() {
assert this instanceof FinalReference;
assert next != null; // I.e. FinalReference is inactive
this.referent = null;
}
@ -413,7 +434,7 @@ public abstract class Reference<T> {
* it was not registered with a queue when it was created
*/
public boolean enqueue() {
this.referent = null;
clear0(); // Intentionally clear0() rather than clear()
return this.queue.enqueue(this);
}

View File

@ -49,3 +49,9 @@ Java_java_lang_ref_Reference_refersTo0(JNIEnv *env, jobject ref, jobject o)
{
return JVM_ReferenceRefersTo(env, ref, o);
}
JNIEXPORT void JNICALL
Java_java_lang_ref_Reference_clear0(JNIEnv *env, jobject ref)
{
JVM_ReferenceClear(env, ref);
}

View File

@ -0,0 +1,96 @@
/*
* Copyright (c) 2020, 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.
*/
package gc;
/* @test
* @bug 8240696
* @library /test/lib
* @build sun.hotspot.WhiteBox
* @modules java.base
* @run main ClassFileInstaller sun.hotspot.WhiteBox
* @run main/othervm
* -Xbootclasspath/a:.
* -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
* gc.TestReferenceClearDuringMarking
*/
import java.lang.ref.WeakReference;
import sun.hotspot.WhiteBox;
public class TestReferenceClearDuringMarking {
private static final WhiteBox WB = WhiteBox.getWhiteBox();
private static Object testA = new Object();
private static Object testB = new Object();
private static final WeakReference<Object> refA1 = new WeakReference<Object>(testA);
private static final WeakReference<Object> refA2 = new WeakReference<Object>(testA);
private static final WeakReference<Object> refB1 = new WeakReference<Object>(testB);
private static final WeakReference<Object> refB2 = new WeakReference<Object>(testB);
private static void test() {
while (!WB.isObjectInOldGen(testA) ||
!WB.isObjectInOldGen(testB) ||
!WB.isObjectInOldGen(refA1) ||
!WB.isObjectInOldGen(refA2) ||
!WB.isObjectInOldGen(refB1) ||
!WB.isObjectInOldGen(refB2)) {
WB.fullGC();
}
WB.concurrentGCAcquireControl();
try {
testA = null;
testB = null;
WB.concurrentGCRunTo(WB.AFTER_MARKING_STARTED);
// Clear A1 early in marking, before reference discovery.
refA1.clear();
WB.concurrentGCRunTo(WB.BEFORE_MARKING_COMPLETED);
// Clear B1 late in marking, after reference discovery.
refB1.clear();
WB.concurrentGCRunToIdle();
// Verify that A2 and B2 still cleared by GC, i.e. the preceding
// clear operations did not extend the lifetime of the referents.
if (!refA2.refersTo(null)) {
throw new RuntimeException("refA2 not cleared");
}
if (!refB2.refersTo(null)) {
throw new RuntimeException("refB2 not cleared");
}
} finally {
WB.concurrentGCReleaseControl();
}
}
public static void main(String[] args) {
if (WB.supportsConcurrentGCBreakpoints()) {
test();
}
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright (c) 2020, 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.
*/
package gc;
/* @test
* @bug 8256517
* @requires vm.gc.Z
* @requires vm.gc != "null"
* @library /test/lib
* @build sun.hotspot.WhiteBox
* @modules java.base
* @run main ClassFileInstaller sun.hotspot.WhiteBox
* @run main/othervm
* -Xbootclasspath/a:.
* -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
* gc.TestReferenceClearDuringReferenceProcessing
*/
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import sun.hotspot.WhiteBox;
public class TestReferenceClearDuringReferenceProcessing {
private static final WhiteBox WB = WhiteBox.getWhiteBox();
private static Object testObject = new Object();
private static final ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
private static final WeakReference<Object> ref = new WeakReference<Object>(testObject, queue);
private static final long TIMEOUT = 10000; // 10sec in millis
private static void test() {
while (!WB.isObjectInOldGen(testObject) ||
!WB.isObjectInOldGen(ref)) {
WB.fullGC();
}
WB.concurrentGCAcquireControl();
try {
testObject = null;
WB.concurrentGCRunTo(WB.AFTER_CONCURRENT_REFERENCE_PROCESSING_STARTED);
if (!ref.refersTo(null)) {
throw new RuntimeException("ref not apparently cleared");
}
ref.clear();
WB.concurrentGCRunToIdle();
Reference<? extends Object> enqueued = null;
try {
enqueued = queue.remove(TIMEOUT);
} catch (InterruptedException e) {
throw new RuntimeException("queue.remove interrupted");
}
if (enqueued == null) {
throw new RuntimeException("ref not enqueued");
} else if (enqueued != ref) {
throw new RuntimeException("some other ref enqeueued");
}
} finally {
WB.concurrentGCReleaseControl();
}
}
public static void main(String[] args) {
if (WB.supportsConcurrentGCBreakpoints()) {
// Also requires concurrent reference processing, but we
// don't have a predicate for that. For now,
// use @requires and CLA to limit the applicable collectors.
test();
}
}
}

View File

@ -450,6 +450,11 @@ public class WhiteBox {
public final String AFTER_MARKING_STARTED = "AFTER MARKING STARTED";
public final String BEFORE_MARKING_COMPLETED = "BEFORE MARKING COMPLETED";
// Collectors supporting concurrent GC breakpoints that do reference
// processing concurrently should provide the following breakpoint.
public final String AFTER_CONCURRENT_REFERENCE_PROCESSING_STARTED =
"AFTER CONCURRENT REFERENCE PROCESSING STARTED";
public void concurrentGCAcquireControl() {
checkConcurrentGCBreakpointsSupported();
if (concurrentGCIsControlled) {