/* * Copyright (c) 2009, 2012, 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 com.sun.max.vm.heap; import static com.sun.max.vm.intrinsics.MaxineIntrinsicIDs.*; import static com.sun.max.vm.jdk.JDK_java_lang_ref_ReferenceQueue.*; import com.sun.max.annotate.*; import com.sun.max.unsafe.*; import com.sun.max.vm.*; import com.sun.max.vm.MaxineVM.Phase; import com.sun.max.vm.actor.holder.*; import com.sun.max.vm.actor.member.*; import com.sun.max.vm.hosted.*; import com.sun.max.vm.jdk.*; import com.sun.max.vm.layout.*; import com.sun.max.vm.log.VMLog.Record; import com.sun.max.vm.log.hosted.*; import com.sun.max.vm.monitor.modal.sync.*; import com.sun.max.vm.object.*; import com.sun.max.vm.reference.*; import com.sun.max.vm.runtime.*; import com.sun.max.vm.thread.*; /** * This class implements support for collecting and processing special references * (i.e. instances of {@link Reference} and its subclasses) which * implement weak references and finalizers. * The routines in this class are called by the GC as it discovers reachable special * references, and after live objects have been processed. */ public class SpecialReferenceManager { private static final boolean FINALIZERS_SUPPORTED = true; /** * This interface forms a contract between the GC algorithm and the implementation of special references. */ public interface GC { /** * Determines if an object is currently in the set of objects that * will survive the current collection. * * @param ref a reference to an object */ boolean isReachable(Reference ref); /** * Ensures that the object graph rooted at a given reference survives the current GC. * WARNING: this interface doesn't update remembered sets if the above relocate the object. * FIXME: may be we should change it, which also requires changing * * @param ref the root of the object graph to be preserved * @return a reference to the root of the preserved object graph. Whether or not this is equal to {@code ref} * depends on the specific GC algorithm (e.g. mark-sweep vs copying) */ Reference preserve(Reference ref); /** * Indicates whether the GC relocates live objects. If true and a reference object is live, the special reference manager must * invoke its {@link #preserve(Reference)} method to update the referent field. * @return true if live objects may have relocated. */ boolean mayRelocateLiveObjects(); } /** * The lock object associated with managing special references. This lock must * be held by the GC when it is updating the list of pending special references. * This value is a reference to the static {@code lock} field in {@link java.lang.ref.Reference}. * Like {@link com.sun.max.vm.thread.VmThreadMap#THREAD_LOCK}, this lock is special and * requires a sticky monitor. */ public static final Object REFERENCE_LOCK = WithoutAccessCheck.getStaticField(JDK.java_lang_ref_Reference.javaClass(), "lock"); static { JavaMonitorManager.bindStickyMonitor(REFERENCE_LOCK); } @FOLD public static int referentIndex() { return JDK.java_lang_ref_Reference.classActor().findLocalInstanceFieldActor("referent").offset() >> Word.widthValue().log2numberOfBytes; } /** * The head of the list of discovered references. * This field must only be used by the GC. Accessing it should not trigger any read/write barriers. */ private static java.lang.ref.Reference discoveredList; /** * An alias type for accessing the fields in java.lang.ref.Reference without having to use reflection. * <p> * The comment below is from the JDK source for java.lang.ref.Reference and explains * the meaning of the {@link #next} and {@link #queue} as pertaining to the state of a reference. * <p> * A Reference instance is in one of four possible internal states: * <p> * Active: Subject to special treatment by the garbage collector. Some * time after the collector detects that the reachability of the * referent has changed to the appropriate state, it changes the * instance's state to either Pending or Inactive, depending upon * whether or not the instance was registered with a queue when it was * created. In the former case it also adds the instance to the * pending-Reference list. Newly-created instances are Active. * <p> * Pending: An element of the pending-Reference list, waiting to be * enqueued by the Reference-handler thread. Unregistered instances * are never in this state. * <p> * Enqueued: An element of the queue with which the instance was * registered when it was created. When an instance is removed from * its ReferenceQueue, it is made Inactive. Unregistered instances are * never in this state. * <p> * Inactive: Nothing more to do. Once an instance becomes Inactive its * state will never change again. * <p> * The state is encoded in the queue and next fields as follows: * <p> * Active: queue = ReferenceQueue with which instance is registered, or * ReferenceQueue.NULL if it was not registered with a queue; next = * null. * <p> * Pending: queue = ReferenceQueue with which instance is registered; * next = Following instance in queue, or this if at end of list.Reference * <p> * Enqueued: queue = ReferenceQueue.ENQUEUED; next = Following instance * in queue, or this if at end of list. * <p> * Inactive: queue = ReferenceQueue.NULL; next = this. * <p> * With this scheme the collector need only examine the next field in order * to determine whether a Reference instance requires special treatment: If * the next field is null then the instance is active; if it is non-null, * then the collector should treat the instance normally. * <p> * To ensure that concurrent collector can discover active Reference * objects without interfering with application threads that may apply * the enqueue() method to those objects, collectors should link * discovered objects through the discovered field. */ public static class JLRRAlias { @ALIAS(declaringClass = java.lang.ref.Reference.class) static java.lang.ref.Reference pending; @ALIAS(declaringClass = java.lang.ref.Reference.class) public java.lang.ref.Reference next; /** * Next ref in a linked list used by the GC to communicate discovered references * from the {@linkplain SpecialReferenceManager#discoverSpecialReference(Pointer) discovery} phase * to the {@linkplain SpecialReferenceManager#processDiscoveredSpecialReferences(GC) processing} phase. */ @ALIAS(declaringClass = java.lang.ref.Reference.class) public java.lang.ref.Reference discovered; @ALIAS(declaringClass = java.lang.ref.Reference.class) Object referent; @ALIAS(declaringClass = java.lang.ref.Reference.class) public java.lang.ref.ReferenceQueue queue; final boolean isActive() { return next == null; } final boolean isPending() { return next != null && queue != ENQUEUED && queue != NULL; } final boolean isEnqueued() { return next != null && queue == ENQUEUED; } final boolean isInactive() { return next != null && queue == NULL; } } @INTRINSIC(UNSAFE_CAST) public static native JLRRAlias asJLRRAlias(Object o); @INTRINSIC(UNSAFE_CAST) public static native java.lang.ref.Reference asJLRR(Object o); /** * This method is called by the GC during heap exploration, when it finds a special * reference object. This method checks to see whether the object has been processed previously, * and if not, then adds it to the queue to be processed later. * * @param cell a pointer at the origin of the reference that has been discovered */ public static void discoverSpecialReference(Pointer cell) { final Pointer origin = Layout.cellToOrigin(cell); java.lang.ref.Reference ref = asJLRR(Reference.fromOrigin(origin)); JLRRAlias refAlias = asJLRRAlias(ref); if (refAlias.discovered == null) { // the discovered field of this object is null, queue it for later processing if (ref == discoveredList) { final boolean lockDisabledSafepoints = Log.lock(); Log.print("Reference "); Log.print(ObjectAccess.readClassActor(ref).name.string); Log.print(" at "); Log.print(cell); Log.println(" is already on discovered list"); Log.unlock(lockDisabledSafepoints); FatalError.unexpected("Duplicate on discovered list"); } final Reference referent = Reference.fromJava(refAlias.referent); // WATCH OUT: the following two lines will cause a write barrier to be executed. Depending on its implementation, this may // resulting in implicit modification to a remember set during GC. refAlias.discovered = discoveredList; discoveredList = ref; if (specialReferenceLogger.enabled()) { specialReferenceLogger.logDiscover(cell, UnsafeCast.asHub(Layout.readHubReference(origin).toJava()).classActor, referent.toOrigin()); } } } /** * Processes the special reference objects that were {@linkplain #discoverSpecialReference(Pointer) discovered} * during heap scanning. * These live reference objects are checked to see whether * the reachability of their "referent" objects has changed according to the type of reference. * If so, they are enqueued as "pending" so that the {@code ReferenceHandler} thread can pick them up * and add them to their respective queues later. * The reference handler lock is notified by the thread that {@linkplain VmOperationThread#submit(VmOperation) submitted} * the GC operation as it holds the lock. See {@link GCOperation#doItEpilogue(boolean)}. * * @param gc interface to the GC implementation */ public static void processDiscoveredSpecialReferences(GC gc) { java.lang.ref.Reference head = discoveredList; java.lang.ref.Reference end = sentinel; final boolean updateReachableReferent = gc.mayRelocateLiveObjects(); if (specialReferenceLogger.enabled()) { specialReferenceLogger.logProcessDiscoveredInit( Reference.fromJava(JDK_java_lang_ref_ReferenceQueue.NULL).toOrigin(), Reference.fromJava(JDK_java_lang_ref_ReferenceQueue.ENQUEUED).toOrigin()); } // Process the discovered list until it is empty (new elements may be // prepended while processing). do { java.lang.ref.Reference ref = head; java.lang.ref.Reference pending = JLRRAlias.pending; while (ref != end) { boolean preserved = false; boolean addedToPending = false; JLRRAlias refAlias = asJLRRAlias(ref); final Reference referent = Reference.fromJava(refAlias.referent); if (referent.isZero()) { // Do not add 'ref' to the pending list as weak references // with already null referents are not added to ReferenceQueues } else if (!gc.isReachable(referent)) { if (refAlias.queue == null) { // This can only occur if there is a GC in the constructor for java.lang.ref.Reference // between the initialization of 'referent' and 'queue'. Log.println("WARNING: cannot add weak reference with null 'queue' field to pending list"); } else { // Only soft and weak references have their referent cleared if (ref instanceof java.lang.ref.SoftReference || ref instanceof java.lang.ref.WeakReference) { refAlias.referent = null; } else { // The following line MUST run the mutator write barrier refAlias.referent = gc.preserve(referent).toJava(); preserved = true; } // Add active reference whose reachability has changed to pending list if (refAlias.isActive()) { if (pending == null) { // 'ref' will be at the end of the pending list refAlias.next = ref; } else { refAlias.next = pending; } pending = ref; addedToPending = true; } } } else if (updateReachableReferent) { // this object is reachable, however the "referent" field was not scanned. // we need to update this field manually // The following line MUST run the mutator write barrier refAlias.referent = gc.preserve(referent).toJava(); } JLRRAlias r = refAlias; ref = refAlias.discovered; r.discovered = null; if (specialReferenceLogger.enabled()) { final Object newReferent = r.referent; specialReferenceLogger.logProcessDiscovered( ObjectAccess.readClassActor(r), ObjectAccess.toOrigin(r), referent.toOrigin(), newReferent == null ? Pointer.zero() : ObjectAccess.toOrigin(newReferent), Reference.fromJava(r.queue).toOrigin(), preserved, updateReachableReferent, addedToPending, MaxineVM.isDebug() ? ObjectAccess.toOrigin(ref) : Pointer.zero()); } } JLRRAlias.pending = pending; if (head == discoveredList) { // No further special references were discovered discoveredList = sentinel; break; } end = head; head = discoveredList; } while (true); } @ALIAS(declaringClassName = "java.lang.ref.Finalizer") private static native void register(Object finalizee); /** * Registers an object that has a finalizer with the special reference manager. * A call to this method is inserted after allocation of such objects. * @param object the object that has a finalizers */ public static void registerFinalizee(Object object) { if (FINALIZERS_SUPPORTED) { FatalError.check(ObjectAccess.readClassActor(object).hasFinalizer(), "cannot register object that has no finalizer"); register(object); if (specialReferenceLogger.enabled()) { specialReferenceLogger.logRegisterFinalizee(Reference.fromJava(object).toOrigin(), ObjectAccess.readClassActor(object)); } } } static final class SentinelReference extends java.lang.ref.WeakReference<Object> { public SentinelReference() { super(null); } } private static final SentinelReference sentinel = new SentinelReference(); /** * Initialize the SpecialReferenceManager when starting the VM. Normally, on the host * VM, the {@link java.lang.ref.Reference} and {@link java.lang.ref.Finalizer} classes create * threads in their static initializers to handle weak references and finalizable objects. * However, in the target VM, these classes have already been initialized and these * threads need to be started manually. * * @param phase the phase in which the VM is in */ public static void initialize(Phase phase) { if (phase == Phase.PRISTINE) { clock = System.currentTimeMillis(); discoveredList = sentinel; JLRRAlias sentinelAlias = asJLRRAlias(sentinel); sentinelAlias.discovered = sentinel; sentinelAlias.next = sentinel; sentinelAlias.referent = null; assert sentinelAlias.isInactive(); startReferenceHandlerThread(); startFinalizerThread(); } } @ALIAS(declaringClass = java.lang.ref.SoftReference.class) private static long clock; @HOSTED_ONLY private static FieldActor getReferenceClassField(String name, Class c) { final ClassActor referenceClass = ClassActor.fromJava(c); FieldActor fieldActor = referenceClass.findLocalStaticFieldActor(name); if (fieldActor == null) { fieldActor = referenceClass.findLocalInstanceFieldActor(name); } return fieldActor; } /** * Start the thread to handle enqueuing weak references. */ private static void startReferenceHandlerThread() { // The thread was built into the boot image. We simply need to start it: VmThread.referenceHandlerThread.startVmSystemThread(); } /** * Allocate and start a new thread to handle invocation of finalizers. */ private static void startFinalizerThread() { if (FINALIZERS_SUPPORTED) { // The thread was built into the boot image. We simply need to start it: VmThread.finalizerThread.startVmSystemThread(); } } // Logging public static final SpecialReferenceLogger specialReferenceLogger = new SpecialReferenceLogger(); // Yet more javac weirdness. Will not compile unless qualified. @com.sun.max.annotate.HOSTED_ONLY @com.sun.max.vm.log.hosted.VMLoggerInterface private interface SpecialReferenceLoggerInterface { void enqueue( @VMLogParam(name = "classActor") ClassActor classActor, @VMLogParam(name = "atOrigin") Pointer atOrigin, @VMLogParam(name = "queueOrigin") Pointer queueOrigin); void discover( @VMLogParam(name = "cell") Pointer cell, @VMLogParam(name = "classActor") ClassActor classActor, @VMLogParam(name = "referentOrigin") Pointer referentOrigin); void registerFinalizee( @VMLogParam(name = "origin") Pointer origin, @VMLogParam(name = "classActor") ClassActor classActor); void processDiscoveredInit( @VMLogParam(name = "nullReferenceQueue") Pointer nullReferenceQueue, @VMLogParam(name = "enqueReferenceQueue") Pointer enqueReferenceQueue); void processDiscovered( @VMLogParam(name = "classActor") ClassActor classActor, @VMLogParam(name = "rOrigin") Pointer rOrigin, @VMLogParam(name = "referentOrigin") Pointer referentOrigin, @VMLogParam(name = "newReferentOrigin") Pointer newReferentOrigin, @VMLogParam(name = "queueOrigin") Pointer queueOrigin, @VMLogParam(name = "stateBools") int stateBools, @VMLogParam(name = "refOrigin") Pointer refOrigin); void remove( @VMLogParam(name = "classActor") ClassActor classActor, @VMLogParam(name = "origin") Pointer origin, @VMLogParam(name = "queueOrigin") Pointer queueOrigin); void processInspectable( @VMLogParam(name = "i") int i, @VMLogParam(name = "rootPointer") Pointer rootPointer, @VMLogParam(name = "value") Word value); } public static final class SpecialReferenceLogger extends SpecialReferenceLoggerAuto { private static final int PRESERVED_BIT = 1; private static final int UPDATE_REACHABLE_REFERENT_BIT = 2; private static final int ADDED_TO_PENDING_BIT = 4; SpecialReferenceLogger() { super("ReferenceGC", "handling of soft/weak/final/phantom references."); } void logProcessDiscovered(ClassActor classActor, Pointer rOrigin, Pointer referentOrigin, Pointer newReferentOrigin, Pointer queueOrigin, boolean preserved, boolean updateReachableReferent, boolean addedToPending, Pointer refOrigin) { // pack booleans to keep arg count <= 8 int stateBools = (preserved ? PRESERVED_BIT : 0) | (updateReachableReferent ? UPDATE_REACHABLE_REFERENT_BIT : 0) | (addedToPending ? ADDED_TO_PENDING_BIT : 0); logProcessDiscovered(classActor, rOrigin, referentOrigin, newReferentOrigin, queueOrigin, stateBools, refOrigin); } @Override public void checkOptions() { super.checkOptions(); checkDominantLoggerOptions(Heap.gcAllLogger); } @Override protected void traceRemove(ClassActor classActor, Pointer origin, Pointer queueOrigin) { Log.printCurrentThread(false); Log.print(": Removed "); Log.print(classActor.name.string); Log.print(" at "); Log.print(origin); Log.print(" from queue "); Log.println(queueOrigin); } @Override protected void traceEnqueue(ClassActor classActor, Pointer atOrigin, Pointer queueOrigin) { Log.printCurrentThread(false); Log.print(": Enqueued "); Log.print(classActor.name.string); Log.print(" at "); Log.print(atOrigin); Log.print(" to queue "); Log.println(queueOrigin); } @Override protected void traceRegisterFinalizee(Pointer origin, ClassActor classActor) { Log.print("Registered finalizer for "); Log.print(origin); Log.print(" of type "); Log.println(classActor.name.string); } @Override protected void traceDiscover(Pointer cell, ClassActor classActor, Pointer referentOrigin) { Log.print("Added "); Log.print(cell); Log.print(' '); Log.print(classActor.name.string); Log.print(" {referent="); Log.print(referentOrigin); Log.println("} to list of discovered references"); } @Override protected void traceProcessDiscoveredInit(Pointer nullReferenceQueue, Pointer enqueReferenceQueue) { Log.print("ReferenceQueue.NULL = "); Log.println(nullReferenceQueue); Log.print("ReferenceQueue.ENQUEUED = "); Log.println(enqueReferenceQueue); } @Override protected void traceProcessDiscovered(ClassActor classActor, Pointer rOrigin, Pointer referentOrigin, Pointer newReferentOrigin, Pointer queueOrigin, int stateBools, Pointer refOrigin) { boolean preserved = (stateBools & PRESERVED_BIT) != 0; boolean updateReachableReferent = (stateBools & UPDATE_REACHABLE_REFERENT_BIT) != 0; boolean addedToPending = (stateBools & ADDED_TO_PENDING_BIT) != 0; Log.print("Processed "); Log.print(classActor.name.string); Log.print(" at "); Log.print(rOrigin); if (MaxineVM.isDebug()) { Log.print(" [next discovered = "); Log.print(refOrigin); Log.print("]"); } Log.print(" whose referent "); Log.print(referentOrigin); if (referentOrigin.isZero()) { Log.print(" was unreachable"); Log.print(" [queue: "); Log.print(queueOrigin); Log.print("]"); } else if (preserved) { Log.print(" was unreachable but preserved to "); Log.print(newReferentOrigin); Log.print(" [queue: "); Log.print(queueOrigin); Log.print("]"); } else if (updateReachableReferent) { Log.print(" moved to "); Log.print(newReferentOrigin); } if (!addedToPending) { Log.print(" {not added to Reference.pending list}"); } Log.println(""); } @Override protected void traceProcessInspectable(int i, Pointer rootPointer, Word value) { Log.print("Processed root table entry "); Log.print(i); Log.print(": set "); Log.print(rootPointer); Log.print(" to "); Log.println(value); } } // START GENERATED CODE private static abstract class SpecialReferenceLoggerAuto extends com.sun.max.vm.log.VMLogger { public enum Operation { Discover, Enqueue, ProcessDiscovered, ProcessDiscoveredInit, ProcessInspectable, RegisterFinalizee, Remove; @SuppressWarnings("hiding") public static final Operation[] VALUES = values(); } private static final int[] REFMAPS = null; protected SpecialReferenceLoggerAuto(String name, String optionDescription) { super(name, Operation.VALUES.length, optionDescription, REFMAPS); } @Override public String operationName(int opCode) { return Operation.VALUES[opCode].name(); } @INLINE public final void logDiscover(Pointer cell, ClassActor classActor, Pointer referentOrigin) { log(Operation.Discover.ordinal(), cell, classActorArg(classActor), referentOrigin); } protected abstract void traceDiscover(Pointer cell, ClassActor classActor, Pointer referentOrigin); @INLINE public final void logEnqueue(ClassActor classActor, Pointer atOrigin, Pointer queueOrigin) { log(Operation.Enqueue.ordinal(), classActorArg(classActor), atOrigin, queueOrigin); } protected abstract void traceEnqueue(ClassActor classActor, Pointer atOrigin, Pointer queueOrigin); @INLINE public final void logProcessDiscovered(ClassActor classActor, Pointer rOrigin, Pointer referentOrigin, Pointer newReferentOrigin, Pointer queueOrigin, int stateBools, Pointer refOrigin) { log(Operation.ProcessDiscovered.ordinal(), classActorArg(classActor), rOrigin, referentOrigin, newReferentOrigin, queueOrigin, intArg(stateBools), refOrigin); } protected abstract void traceProcessDiscovered(ClassActor classActor, Pointer rOrigin, Pointer referentOrigin, Pointer newReferentOrigin, Pointer queueOrigin, int stateBools, Pointer refOrigin); @INLINE public final void logProcessDiscoveredInit(Pointer nullReferenceQueue, Pointer enqueReferenceQueue) { log(Operation.ProcessDiscoveredInit.ordinal(), nullReferenceQueue, enqueReferenceQueue); } protected abstract void traceProcessDiscoveredInit(Pointer nullReferenceQueue, Pointer enqueReferenceQueue); @INLINE public final void logProcessInspectable(int i, Pointer rootPointer, Word value) { log(Operation.ProcessInspectable.ordinal(), intArg(i), rootPointer, value); } protected abstract void traceProcessInspectable(int i, Pointer rootPointer, Word value); @INLINE public final void logRegisterFinalizee(Pointer origin, ClassActor classActor) { log(Operation.RegisterFinalizee.ordinal(), origin, classActorArg(classActor)); } protected abstract void traceRegisterFinalizee(Pointer origin, ClassActor classActor); @INLINE public final void logRemove(ClassActor classActor, Pointer origin, Pointer queueOrigin) { log(Operation.Remove.ordinal(), classActorArg(classActor), origin, queueOrigin); } protected abstract void traceRemove(ClassActor classActor, Pointer origin, Pointer queueOrigin); @Override protected void trace(Record r) { switch (r.getOperation()) { case 0: { //Discover traceDiscover(toPointer(r, 1), toClassActor(r, 2), toPointer(r, 3)); break; } case 1: { //Enqueue traceEnqueue(toClassActor(r, 1), toPointer(r, 2), toPointer(r, 3)); break; } case 2: { //ProcessDiscovered traceProcessDiscovered(toClassActor(r, 1), toPointer(r, 2), toPointer(r, 3), toPointer(r, 4), toPointer(r, 5), toInt(r, 6), toPointer(r, 7)); break; } case 3: { //ProcessDiscoveredInit traceProcessDiscoveredInit(toPointer(r, 1), toPointer(r, 2)); break; } case 4: { //ProcessInspectable traceProcessInspectable(toInt(r, 1), toPointer(r, 2), toWord(r, 3)); break; } case 5: { //RegisterFinalizee traceRegisterFinalizee(toPointer(r, 1), toClassActor(r, 2)); break; } case 6: { //Remove traceRemove(toClassActor(r, 1), toPointer(r, 2), toPointer(r, 3)); break; } } } } // END GENERATED CODE }