/*
* This file is part of the Jikes RVM project (http://jikesrvm.org).
*
* This file is licensed to You under the Eclipse Public License (EPL);
* You may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.
*/
package org.jikesrvm.mm.mmtk;
import org.mmtk.plan.Plan;
import org.mmtk.plan.TraceLocal;
import org.mmtk.utility.options.Options;
import org.vmmagic.pragma.*;
import org.vmmagic.unboxed.*;
import org.jikesrvm.VM;
import org.jikesrvm.mm.mminterface.DebugUtil;
import org.jikesrvm.mm.mminterface.Selected;
import org.jikesrvm.runtime.Entrypoints;
import org.jikesrvm.runtime.Magic;
import org.jikesrvm.scheduler.RVMThread;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.lang.ref.PhantomReference;
/**
* This class manages SoftReferences, WeakReferences, and
* PhantomReferences. When a java/lang/ref/Reference object is created,
* its address is added to a table of pending reference objects of the
* appropriate type. An address is used so the reference will not stay
* alive during gc if it isn't in use elsewhere the mutator. During
* gc, the various lists are processed in the proper order to
* determine if any Reference objects are ready to be enqueued or
* whether referents that have died should be kept alive until the
* Reference is explicitly cleared. MMTk drives this processing and
* uses this class, via the VM interface, to scan the lists of pending
* reference objects.
*
* As an optimization for generational collectors, each reference type
* maintains two queues: a nursery queue and the main queue.
*/
@Uninterruptible
public final class ReferenceProcessor extends org.mmtk.vm.ReferenceProcessor {
/********************************************************************
* Class fields
*/
private static final Lock lock = new Lock("ReferenceProcessor");
private static final ReferenceProcessor softReferenceProcessor =
new ReferenceProcessor(Semantics.SOFT);
private static final ReferenceProcessor weakReferenceProcessor =
new ReferenceProcessor(Semantics.WEAK);
private static final ReferenceProcessor phantomReferenceProcessor =
new ReferenceProcessor(Semantics.PHANTOM);
// Debug flags
private static final boolean TRACE = false;
private static final boolean TRACE_UNREACHABLE = false;
private static final boolean TRACE_DETAIL = false;
private static final boolean STRESS = false || VM.ForceFrequentGC;
/** Initial size of the reference object table */
private static final int INITIAL_SIZE = STRESS ? 1 : 256;
/**
* Grow the reference object table by this multiplier
* on overflow
*/
private static final double GROWTH_FACTOR = 2.0;
/*************************************************************************
* Instance fields
*/
/**
* The table of reference objects for the current semantics
*/
private volatile AddressArray references = AddressArray.create(INITIAL_SIZE);
/**
* In a MarkCompact (or similar) collector, we need to update the {@code references}
* field, and then update its contents. We implement this by saving the pointer in
* this untraced field for use during the {@code forward} pass.
*/
@Untraced
private volatile AddressArray unforwardedReferences = null;
/**
* Index into the <code>references</code> table for the start of
* the reference nursery.
*/
private int nurseryIndex = 0;
/**
* Index of the first free slot in the reference table.
*/
private volatile int maxIndex = 0;
/**
* Flag to prevent a race between threads growing the reference object
* table.
*/
private volatile boolean growingTable = false;
/**
* Semantics
*/
private final Semantics semantics;
/** Copy of semantics.toString() for use in uninterruptible code */
private final String semanticsStr;
/**
* Create a reference processor for a given semantics
*
* @param semantics
*/
private ReferenceProcessor(Semantics semantics) {
this.semantics = semantics;
this.semanticsStr = semantics.toString();
}
/**
* Factory method.
* Creates an instance of the appropriate reference type processor.
* @return the reference processor
*/
@Interruptible
public static ReferenceProcessor get(Semantics semantics) {
switch(semantics) {
case WEAK: return weakReferenceProcessor;
case SOFT: return softReferenceProcessor;
case PHANTOM: return phantomReferenceProcessor;
default:
VM._assert(false,"Unrecognized semantics");
return null;
}
}
/**
* Add a reference at the end of the table
* @param ref The reference to add
*/
private void addReference(Reference<?> ref, ObjectReference referent) {
ObjectReference reference = ObjectReference.fromObject(ref);
setReferent(reference, referent);
setReference(maxIndex++,reference);
}
/**
* Update the reference table
*
* @param i The table index
* @param ref The reference to insert
*/
private void setReference(int i, ObjectReference ref) {
if (TRACE_DETAIL) {
VM.sysWrite("slot ",i);
VM.sysWriteln(" => ",ref);
}
references.set(i,ref.toAddress());
}
/**
* Retrieve from the reference table
*
* @param i Table index
* @return The reference object at index i
*/
private ObjectReference getReference(int i) {
return references.get(i).toObjectReference();
}
/**
* Grow the reference table by GROWTH_FACTOR.
*
* <p>Logically Uninterruptible because it can GC when it allocates, but
* the rest of the code can't tolerate GC.
*
* <p>This method is called without the reference processor lock held,
* but with the flag <code>growingTable</code> set.
*/
@UninterruptibleNoWarn
private AddressArray growReferenceTable() {
int newLength = STRESS ? references.length() + 1 : (int)(references.length() * GROWTH_FACTOR);
if (TRACE) VM.sysWriteln("Expanding reference type table ",semanticsStr," to ",newLength);
AddressArray newReferences = AddressArray.create(newLength);
for (int i=0; i < references.length(); i++)
newReferences.set(i,references.get(i));
return newReferences;
}
/**
* Add a reference to the list of references. This method is responsible
* for installing the address of the referent into the Reference object
* so that the referent is traced at all yield points before the Reference
* is correctly installed in the reference table.
*
* (SJF: This method must NOT be inlined into an inlined allocation
* sequence, since it contains a lock!)
*
* @param referent The referent of the reference
* @param ref The reference to add
*/
@NoInline
@Unpreemptible("Non-preemptible but yield when table needs to be grown")
private void addCandidate(Reference<?> ref, ObjectReference referent) {
if (TRACE) {
ObjectReference referenceAsAddress = ObjectReference.fromObject(ref);
VM.sysWrite("Adding Reference: ", referenceAsAddress);
VM.sysWriteln(" ~> ", referent);
}
/*
* Ensure that only one thread at a time can grow the
* table of references. The volatile flag <code>growingTable</code> is
* used to allow growing the table to trigger GC, but to prevent
* any other thread from accessing the table while it is being grown.
*
* If the table has space, threads will add the reference, incrementing maxIndex
* and exit.
*
* If the table is full, the first thread to notice will grow the table.
* Subsequent threads will release the lock and yield at (1) while the
* first thread
*/
lock.acquire();
while (growingTable || maxIndex >= references.length()) {
if (growingTable) {
// FIXME: We should probably speculatively allocate a new table instead.
// note, we can copy without the lock after installing the new table (unint during copy).
lock.release();
RVMThread.yield(); // (1) Allow another thread to grow the table
lock.acquire();
} else {
growingTable = true; // Prevent other threads from growing table while lock is released
lock.release(); // Can't hold the lock while allocating
AddressArray newTable = growReferenceTable();
lock.acquire();
references = newTable;
growingTable = false; // Allow other threads to grow the table rather than waiting for us
}
}
addReference(ref,referent);
lock.release();
}
/***********************************************************************
* GC time processing
*/
/**
* Scan through all references and forward.
*
* Collectors like MarkCompact determine liveness and move objects
* using separate traces.
*
* Currently ignores the nursery hint.
*
* TODO parallelise this code
*
* @param trace The trace
* @param nursery Is this a nursery collection ?
*/
@Override
public void forward(TraceLocal trace, boolean nursery) {
if (VM.VerifyAssertions) VM._assert(unforwardedReferences != null);
if (TRACE) VM.sysWriteln("Starting ReferenceGlue.forward(",semanticsStr,")");
if (TRACE_DETAIL) {
VM.sysWrite(semanticsStr," Reference table is ",
Magic.objectAsAddress(references));
VM.sysWriteln("unforwardedReferences is ",
Magic.objectAsAddress(unforwardedReferences));
}
for (int i=0; i < maxIndex; i++) {
if (TRACE_DETAIL) VM.sysWrite("slot ",i,": ");
ObjectReference reference = unforwardedReferences.get(i).toObjectReference();
if (TRACE_DETAIL) VM.sysWriteln("forwarding ",reference);
setReferent(reference, trace.getForwardedReferent(getReferent(reference)));
ObjectReference newReference = trace.getForwardedReference(reference);
unforwardedReferences.set(i, newReference.toAddress());
}
if (TRACE) VM.sysWriteln("Ending ReferenceGlue.forward(",semanticsStr,")");
unforwardedReferences = null;
}
/**
* Clear the contents of the table. This is called when reference types are
* disabled to make it easier for VMs to change this setting at runtime.
*/
@Override
public void clear() {
maxIndex = 0;
}
/**
* Scan through the list of references. Calls ReferenceProcessor's
* processReference method for each reference and builds a new
* list of those references still active.
*
* Depending on the value of <code>nursery</code>, we will either
* scan all references, or just those created since the last scan.
*
* TODO parallelise this code
*
* @param nursery Scan only the newly created references
*/
@Override
public void scan(TraceLocal trace, boolean nursery) {
unforwardedReferences = references;
if (TRACE) VM.sysWriteln("Starting ReferenceGlue.scan(",semanticsStr,")");
int toIndex = nursery ? nurseryIndex : 0;
if (TRACE_DETAIL) VM.sysWriteln(semanticsStr," Reference table is ",Magic.objectAsAddress(references));
for (int fromIndex = toIndex; fromIndex < maxIndex; fromIndex++) {
ObjectReference reference = getReference(fromIndex);
/* Determine liveness (and forward if necessary) the reference */
ObjectReference newReference = processReference(trace,reference);
if (!newReference.isNull()) {
setReference(toIndex++,newReference);
if (TRACE_DETAIL) {
int index = toIndex-1;
VM.sysWrite("SCANNED ",index);
VM.sysWrite(" ",references.get(index));
VM.sysWrite(" -> ");
VM.sysWriteln(getReferent(references.get(index).toObjectReference()));
}
}
}
if (Options.verbose.getValue() >= 3) {
VM.sysWrite(semanticsStr);
VM.sysWriteln(" references: ",maxIndex," -> ",toIndex);
}
nurseryIndex = maxIndex = toIndex;
/* flush out any remset entries generated during the above activities */
Selected.Mutator.get().flushRememberedSets();
if (TRACE) VM.sysWriteln("Ending ReferenceGlue.scan(",semanticsStr,")");
}
/**
* Put this Reference object on its ReferenceQueue (if it has one)
* when its referent is no longer sufficiently reachable. The
* definition of "reachable" is defined by the semantics of the
* particular subclass of Reference. The implementation of this
* routine is determined by the the implementation of
* java.lang.ref.ReferenceQueue in GNU classpath. It is in this
* class rather than the public Reference class to ensure that Jikes
* has a safe way of enqueueing the object, one that cannot be
* overridden by the application program.
*
* ************************ TODO *********************************
* Change this so that we don't call reference.enqueue directly
* as this can be overridden by the user.
* ***************************************************************
*
* @see java.lang.ref.ReferenceQueue
* @param addr the address of the Reference object
* @return <code>true</code> if the reference was enqueued
*/
@Unpreemptible
public boolean enqueueReference(ObjectReference addr) {
Reference<?> reference = (Reference<?>)addr.toObject();
return reference.enqueueInternal();
}
/**
* Add a reference to the list of soft references.
* @param ref the SoftReference to add
*/
@Interruptible
public static void addSoftCandidate(SoftReference<?> ref, ObjectReference referent) {
softReferenceProcessor.addCandidate(ref, referent);
}
/**
* Add a reference to the list of weak references.
* @param ref the WeakReference to add
*/
@Interruptible
public static void addWeakCandidate(WeakReference<?> ref, ObjectReference referent) {
weakReferenceProcessor.addCandidate(ref, referent);
}
/**
* Add a reference to the list of phantom references.
* @param ref the PhantomReference to add
*/
@Interruptible
public static void addPhantomCandidate(PhantomReference<?> ref, ObjectReference referent) {
phantomReferenceProcessor.addCandidate(ref, referent);
}
/****************************************************************************
*
* Semantics of reference types
*
*/
/**
* Process a reference with the current semantics.
* @param reference the address of the reference. This may or may not
* be the address of a heap object, depending on the VM.
* @param trace the thread local trace element.
*/
@UninterruptibleNoWarn("Call out to ReferenceQueue API")
public ObjectReference processReference(TraceLocal trace, ObjectReference reference) {
if (VM.VerifyAssertions) VM._assert(!reference.isNull());
if (TRACE_DETAIL) {
VM.sysWrite("Processing reference: ",reference);
}
/*
* If the reference is dead, we're done with it. Let it (and
* possibly its referent) be garbage-collected.
*/
if (!trace.isLive(reference)) {
clearReferent(reference); // Too much paranoia ...
if (TRACE_UNREACHABLE) { VM.sysWriteln(" UNREACHABLE reference: ",reference); }
if (TRACE_DETAIL) {
VM.sysWriteln(" (unreachable)");
}
return ObjectReference.nullReference();
}
/* The reference object is live */
ObjectReference newReference = trace.getForwardedReference(reference);
ObjectReference oldReferent = getReferent(reference);
if (TRACE_DETAIL) {
VM.sysWrite(" ~> ",oldReferent);
}
/*
* If the application has cleared the referent the Java spec says
* this does not cause the Reference object to be enqueued. We
* simply allow the Reference object to fall out of our
* waiting list.
*/
if (oldReferent.isNull()) {
if (TRACE_DETAIL) VM.sysWriteln(" (null referent)");
return ObjectReference.nullReference();
}
if (TRACE_DETAIL) VM.sysWrite(" => ",newReference);
if (semantics == Semantics.SOFT) {
/*
* Unless we've completely run out of memory, we keep
* softly reachable objects alive.
*/
if (!Plan.isEmergencyCollection()) {
if (TRACE_DETAIL) VM.sysWrite(" (soft) ");
trace.retainReferent(oldReferent);
}
} else if (semantics == Semantics.PHANTOM) {
/*
* The spec says we should forward the reference. Without unsafe uses of
* reflection, the application can't tell the difference whether we do or not,
* so we don't forward the reference.
*/
// trace.retainReferent(oldReferent);
}
if (trace.isLive(oldReferent)) {
if (VM.VerifyAssertions) {
if (!DebugUtil.validRef(oldReferent)) {
VM.sysWriteln("Error in old referent.");
DebugUtil.dumpRef(oldReferent);
VM.sysFail("Invalid reference");
}
}
/*
* Referent is still reachable in a way that is as strong as
* or stronger than the current reference level.
*/
ObjectReference newReferent = trace.getForwardedReferent(oldReferent);
if (TRACE_DETAIL) VM.sysWriteln(" ~> ",newReferent);
if (VM.VerifyAssertions) {
if (!DebugUtil.validRef(newReferent)) {
VM.sysWriteln("Error forwarding reference object.");
DebugUtil.dumpRef(oldReferent);
VM.sysFail("Invalid reference");
}
VM._assert(trace.isLive(newReferent));
}
/*
* The reference object stays on the waiting list, and the
* referent is untouched. The only thing we must do is
* ensure that the former addresses are updated with the
* new forwarding addresses in case the collector is a
* copying collector.
*/
/* Update the referent */
setReferent(newReference, newReferent);
return newReference;
} else {
/* Referent is unreachable. Clear the referent and enqueue the reference object. */
if (TRACE_DETAIL) VM.sysWriteln(" UNREACHABLE");
else if (TRACE_UNREACHABLE) VM.sysWriteln(" UNREACHABLE referent: ",oldReferent);
clearReferent(newReference);
enqueueReference(newReference);
return ObjectReference.nullReference();
}
}
/**
* Weak and soft references always clear the referent
* before enqueueing. We don't actually call
* Reference.clear() as the user could have overridden the
* implementation and we don't want any side-effects to
* occur.
*/
protected void clearReferent(ObjectReference newReference) {
setReferent(newReference, ObjectReference.nullReference());
}
/***********************************************************************
*
* Reference object field accessors
*/
/**
* Get the referent from a reference. For Java the reference
* is a Reference object.
* @param object the object reference.
* @return the referent object reference.
*/
protected ObjectReference getReferent(ObjectReference object) {
if (VM.VerifyAssertions) VM._assert(!object.isNull());
return object.toAddress().loadObjectReference(Entrypoints.referenceReferentField.getOffset());
}
/**
* Set the referent in a reference. For Java the reference is
* a Reference object.
* @param ref the ObjectReference for the reference (confusing eh?).
* @param referent the referent object reference.
*/
protected void setReferent(ObjectReference ref, ObjectReference referent) {
ref.toAddress().store(referent, Entrypoints.referenceReferentField.getOffset());
if (Selected.Constraints.get().needsJavaLangReferenceUpdateNotify())
Selected.Mutator.get().javaLangReferenceUpdated(ref, referent);
}
/***********************************************************************
*
* Statistics and debugging
*/
public int countWaitingReferences() {
return maxIndex;
}
}