diff --git a/jdk/src/java.base/share/classes/java/lang/ref/Reference.java b/jdk/src/java.base/share/classes/java/lang/ref/Reference.java index d18f059308f..eeb771903af 100644 --- a/jdk/src/java.base/share/classes/java/lang/ref/Reference.java +++ b/jdk/src/java.base/share/classes/java/lang/ref/Reference.java @@ -25,6 +25,7 @@ package java.lang.ref; +import jdk.internal.vm.annotation.DontInline; import sun.misc.Cleaner; import jdk.internal.HotSpotIntrinsicCandidate; import jdk.internal.misc.JavaLangRefAccess; @@ -311,4 +312,120 @@ public abstract class Reference { this.queue = (queue == null) ? ReferenceQueue.NULL : queue; } + /** + * Ensures that the object referenced by the given reference remains + * strongly reachable, + * regardless of any prior actions of the program that might otherwise cause + * the object to become unreachable; thus, the referenced object is not + * reclaimable by garbage collection at least until after the invocation of + * this method. Invocation of this method does not itself initiate garbage + * collection or finalization. + * + *

This method establishes an ordering for + * strong reachability + * with respect to garbage collection. It controls relations that are + * otherwise only implicit in a program -- the reachability conditions + * triggering garbage collection. This method is designed for use in + * uncommon situations of premature finalization where using + * {@code synchronized} blocks or methods, or using other synchronization + * facilities are not possible or do not provide the desired control. This + * method is applicable only when reclamation may have visible effects, + * which is possible for objects with finalizers (See + * + * Section 12.6 17 of The Java™ Language Specification) + * that are implemented in ways that rely on ordering control for correctness. + * + * @apiNote + * Finalization may occur whenever the virtual machine detects that no + * reference to an object will ever be stored in the heap: The garbage + * collector may reclaim an object even if the fields of that object are + * still in use, so long as the object has otherwise become unreachable. + * This may have surprising and undesirable effects in cases such as the + * following example in which the bookkeeping associated with a class is + * managed through array indices. Here, method {@code action} uses a + * {@code reachabilityFence} to ensure that the {@code Resource} object is + * not reclaimed before bookkeeping on an associated + * {@code ExternalResource} has been performed; in particular here, to + * ensure that the array slot holding the {@code ExternalResource} is not + * nulled out in method {@link Object#finalize}, which may otherwise run + * concurrently. + * + *

 {@code
+     * class Resource {
+     *   private static ExternalResource[] externalResourceArray = ...
+     *
+     *   int myIndex;
+     *   Resource(...) {
+     *     myIndex = ...
+     *     externalResourceArray[myIndex] = ...;
+     *     ...
+     *   }
+     *   protected void finalize() {
+     *     externalResourceArray[myIndex] = null;
+     *     ...
+     *   }
+     *   public void action() {
+     *     try {
+     *       // ...
+     *       int i = myIndex;
+     *       Resource.update(externalResourceArray[i]);
+     *     } finally {
+     *       Reference.reachabilityFence(this);
+     *     }
+     *   }
+     *   private static void update(ExternalResource ext) {
+     *     ext.status = ...;
+     *   }
+     * }}
+ * + * Here, the invocation of {@code reachabilityFence} is nonintuitively + * placed after the call to {@code update}, to ensure that the + * array slot is not nulled out by {@link Object#finalize} before the + * update, even if the call to {@code action} was the last use of this + * object. This might be the case if, for example a usage in a user program + * had the form {@code new Resource().action();} which retains no other + * reference to this {@code Resource}. While probably overkill here, + * {@code reachabilityFence} is placed in a {@code finally} block to ensure + * that it is invoked across all paths in the method. In a method with more + * complex control paths, you might need further precautions to ensure that + * {@code reachabilityFence} is encountered along all of them. + * + *

It is sometimes possible to better encapsulate use of + * {@code reachabilityFence}. Continuing the above example, if it were + * acceptable for the call to method {@code update} to proceed even if the + * finalizer had already executed (nulling out slot), then you could + * localize use of {@code reachabilityFence}: + * + *

 {@code
+     * public void action2() {
+     *   // ...
+     *   Resource.update(getExternalResource());
+     * }
+     * private ExternalResource getExternalResource() {
+     *   ExternalResource ext = externalResourceArray[myIndex];
+     *   Reference.reachabilityFence(this);
+     *   return ext;
+     * }}
+ * + *

Method {@code reachabilityFence} is not required in constructions + * that themselves ensure reachability. For example, because objects that + * are locked cannot, in general, be reclaimed, it would suffice if all + * accesses of the object, in all methods of class {@code Resource} + * (including {@code finalize}) were enclosed in {@code synchronized (this)} + * blocks. (Further, such blocks must not include infinite loops, or + * themselves be unreachable, which fall into the corner case exceptions to + * the "in general" disclaimer.) However, method {@code reachabilityFence} + * remains a better option in cases where this approach is not as efficient, + * desirable, or possible; for example because it would encounter deadlock. + * + * @param ref the reference. If {@code null}, this method has no effect. + * @since 9 + */ + @DontInline + public static void reachabilityFence(Object ref) { + // Does nothing, because this method is annotated with @DontInline + // HotSpot needs to retain the ref and not GC it before a call to this + // method + } + } diff --git a/jdk/test/java/lang/ref/ReachabilityFenceTest.java b/jdk/test/java/lang/ref/ReachabilityFenceTest.java new file mode 100644 index 00000000000..461c6b200f1 --- /dev/null +++ b/jdk/test/java/lang/ref/ReachabilityFenceTest.java @@ -0,0 +1,143 @@ +/* + * 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. + */ + +/* @test + * @bug 8133348 + * @summary Tests if reachabilityFence is working + * + * @run main/othervm -Xint -Dpremature=false ReachabilityFenceTest + * @run main/othervm -XX:TieredStopAtLevel=1 -Dpremature=true ReachabilityFenceTest + * @run main/othervm -XX:TieredStopAtLevel=2 -Dpremature=true ReachabilityFenceTest + * @run main/othervm -XX:TieredStopAtLevel=3 -Dpremature=true ReachabilityFenceTest + * @run main/othervm -XX:TieredStopAtLevel=4 -Dpremature=true ReachabilityFenceTest + */ + +import java.lang.ref.Reference; +import java.util.concurrent.atomic.AtomicBoolean; + +public class ReachabilityFenceTest { + + /* + * Implementation notes: + * + * This test has positive and negative parts. + * + * Negative test is "nonFenced", and it tests that absent of reachabilityFence, the object can + * be prematurely finalized -- this validates the test itself. Not every VM mode is expected to + * prematurely finalize the objects, and -Dpremature option communicates that to test. If a VM mode + * passes the negative test, then our understanding of what could happen is correct, and we can + * go forward. + * + * Positive test is "fenced", and it checks that given the reachabilityFence at the end of the block, + * the object cannot be finalized. There is no sense running a positive test when premature finalization + * is not expected. It is a job for negative test to verify that invariant. + * + * The test methods should be appropriately compiled, therefore we do several iterations. + */ + + // Enough to OSR and compile + static final int LOOP_ITERS = Integer.getInteger("LOOP_ITERS", 50000); + + // Enough after which to start triggering GC and finalization + static final int WARMUP_LOOP_ITERS = LOOP_ITERS - Integer.getInteger("GC_ITERS", 100); + + // Enough to switch from an OSR'ed method to compiled method + static final int MAIN_ITERS = 3; + + static final boolean PREMATURE_FINALIZATION = Boolean.getBoolean("premature"); + + public static void main(String... args) { + // Negative test + boolean finalized = false; + for (int c = 0; !finalized && c < MAIN_ITERS; c++) { + finalized |= nonFenced(); + } + + if (PREMATURE_FINALIZATION && !finalized) { + throw new IllegalStateException("The object had never been finalized before timeout reached."); + } + + if (!PREMATURE_FINALIZATION && finalized) { + throw new IllegalStateException("The object had been finalized without a fence, even though we don't expect it."); + } + + if (!PREMATURE_FINALIZATION) + return; + + // Positive test + finalized = false; + for (int c = 0; !finalized && c < MAIN_ITERS; c++) { + finalized |= fenced(); + } + + if (finalized) { + throw new IllegalStateException("The object had been prematurely finalized."); + } + } + + public static boolean nonFenced() { + AtomicBoolean finalized = new AtomicBoolean(); + MyFinalizeable o = new MyFinalizeable(finalized); + + for (int i = 0; i < LOOP_ITERS; i++) { + if (finalized.get()) break; + if (i > WARMUP_LOOP_ITERS) { + System.gc(); + System.runFinalization(); + } + } + + return finalized.get(); + } + + public static boolean fenced() { + AtomicBoolean finalized = new AtomicBoolean(); + MyFinalizeable o = new MyFinalizeable(finalized); + + for (int i = 0; i < LOOP_ITERS; i++) { + if (finalized.get()) break; + if (i > WARMUP_LOOP_ITERS) { + System.gc(); + System.runFinalization(); + } + } + + Reference.reachabilityFence(o); + + return finalized.get(); + } + + private static class MyFinalizeable { + private final AtomicBoolean finalized; + + public MyFinalizeable(AtomicBoolean b) { + this.finalized = b; + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + finalized.set(true); + } + } +} \ No newline at end of file