/*
* This file is part of the Jikes RVM project (http://jikesrvm.org).
*
* This file is licensed to You under the Common Public License (CPL);
* 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/cpl1.0.php
*
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.
*/
package org.mmtk.utility;
import org.mmtk.plan.TraceLocal;
import org.mmtk.vm.Lock;
import org.mmtk.vm.VM;
import org.mmtk.vm.SynchronizedCounter;
import org.vmmagic.pragma.*;
import org.vmmagic.unboxed.*;
/**
* This class manages finalization. When an object is created if its
* class has a finalize() method, addElement below is called, and a
* FinalizerListElement (see FinalizerListElement) is created for it.
* While the object is old, the integer field of the list element
* holds its value (this does not keep the object live during gc. At
* the end of gc, the list of FinalizerListElements is scanned for
* objects which have become garbage. Those which have are made live
* again are moved to the live object list for finalization.
*
* Elsewhere, there is a distinguished Finalizer thread which
* enqueues itself on the VM_Scheduler finalizerQueue. At the end of gc,
* if needed and if the VM_Scheduler finalizerQueue is not empty,
* the finalizer thread is scheduled to be run when gc is completed.
*/
@Uninterruptible public class Finalizer {
// ----------------//
// Implementation //
// ----------------//
private static int INITIAL_SIZE = 32768;
private static double growthFactor = 2.0;
private static final Lock lock = VM.newLock("Finalizer");
private static final SynchronizedCounter gcLock = VM.newSynchronizedCounter();
/* Use an AddressArray rather than ObjectReference array to *avoid* this
being traced. We don't want this array to keep the candiates alive */
private static AddressArray candidate = AddressArray.create(INITIAL_SIZE);
private static int candidateEnd; // candidate[0] .. candidate[candidateEnd-1] contains non-zero entries
private static ObjectReferenceArray live = ObjectReferenceArray.create(INITIAL_SIZE);
private static int liveStart; // live[liveStart] .. live[liveEnd-1] are the non-null entries
private static int liveEnd;
// -----------//
// interface //
// -----------//
// Add item.
//
@Interruptible
@NoInline
// (SJF: This method must NOT be inlined into an inlined allocation sequence, since it contains a lock!)
//
public static void addCandidate(ObjectReference item) {
/* The following is tricky due to its littering of deadlock potential and
* thread (logical and physical) race conditions, hence the unusual comment
* verbosity.
*
* If we know we need to expand newCandidate array, then do a preventive
* full collection. This prevents two-stage deadlock arising when
* AddressArray.create(int) must adjust page tables, requiring acquiring
* the immortal mutator lock which might be held by another thread which
* attempts to add a finalizer candidate also while simultaneously holding
* the immortal mutator lock.
*/
if (candidateEnd >= candidate.length()) {
/* must guard against multiple logical or physical processors executing
* mutator threads which enter this method concurrently. the first thread
* in should commence the collection; all subsequent threads must block,
* until the GC succeeds. Note we cannot invoke System.gc() here, since
* that method will return immediately if collection is already in-
* progress.
*/
if (gcLock.increment() == 0) { // lead concurrent mutator
/* Note the above lock is insufficient to guarantee that only a single
* thread is in explicit collection (since another thread could have
* executed System.gc() directly). Thus, use System.gc() here, rather than
* org.mmtk.vm.Collection.triggerCollection(org.mmtk.vm.Collection.EXTERNAL_GC_TRIGGER),
* to prevent collection from being concurrently executed. See RVM
* bug 1511447 and RVM patch 1512948 for more detail.
*/
try {
System.gc();
} finally {
// release trailing concurrent mutators (in while-loop below)
gcLock.reset();
}
} else { // trailing concurrent mutators
// wait until lead thread finishes above collection
while (gcLock.peek() != 0) {
/* must do a yielded-spin, since the concurrent
* thread may be on the same physically processor.
* plus, gc may take a long time and we should
* hog as few cycles as possible while we wait patiently.
*/
Thread.yield();
}
}
}
try {
lock.acquire();
int origLength = candidate.length();
if (candidateEnd >= origLength) {
/* the above explicit collection will ensure this
* does not deadlock due to necessity for physical
* memory management (e.g. virtual page allocation)
* to satisfy this allocation request.
*/
AddressArray newCandidate = AddressArray.create((int) (growthFactor * origLength));
for (int i=0; i<origLength; i++) {
newCandidate.set(i, candidate.get(i));
}
candidate = newCandidate;
}
candidate.set(candidateEnd++, item.toAddress());
} finally {
lock.release();
}
}
private static void compactCandidates() {
int leftCursor = 0;
int rightCursor = candidateEnd - 1;
// Invariant: Slots left of leftCursor are non-empty and slots right of rightCursor are empty
while (true) {
// Advance left cursor until it hits empty slot
while (leftCursor < rightCursor && !candidate.get(leftCursor).isZero())
leftCursor++;
// Back-advance right cursor until it hits non-empty slot
while (rightCursor > leftCursor && candidate.get(rightCursor).isZero())
rightCursor--;
if (leftCursor >= rightCursor) // can be greater on first iteration if totally empty
break;
if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(candidate.get(leftCursor).isZero() && !candidate.get(rightCursor).isZero());
candidate.set(leftCursor, candidate.get(rightCursor));
candidate.set(rightCursor, Address.zero());
}
if (candidate.get(leftCursor).isZero())
candidateEnd = leftCursor;
else
candidateEnd = leftCursor + 1;
}
/* Add revived object that needs to be finalized
*
* The aastore is actually uninterruptible since the target is an array of Objects.
*/
@LogicallyUninterruptible
private static void addLive(ObjectReference obj) {
if (liveEnd == live.length()) {
ObjectReferenceArray newLive = live;
if (liveStart == 0)
newLive = ObjectReferenceArray.create((int) (growthFactor * live.length()));
for (int i = liveStart; i < liveEnd; i++)
newLive.set(i - liveStart, live.get(i));
for (int i = liveEnd - liveStart; i < live.length(); i++)
newLive.set(i, ObjectReference.nullReference());
liveEnd -= liveStart;
liveStart = 0;
live = newLive;
}
live.set(liveEnd++, obj);
}
/**
* Called from the mutator thread: return the first object queued on
* the finalize list, or null if none
*
* The aastore is actually uninterruptible since the target is an
* array of Objects.
*/
@LogicallyUninterruptible
public static ObjectReference get() {
if (liveStart == liveEnd) return ObjectReference.nullReference();
ObjectReference obj = live.get(liveStart);
live.set(liveStart++, ObjectReference.nullReference());
return obj;
}
/**
* Move all finalizable objects to the to-be-finalized queue
* Called on shutdown. Caller must also schedule the finalizer thread.
*/
public static void finalizeAll() {
int cursor = 0;
while (cursor < candidateEnd) {
Address cand = candidate.get(cursor);
candidate.set(cursor, Address.zero());
addLive(cand.toObjectReference());
cursor++;
}
compactCandidates();
}
public static void kill() {
candidateEnd = 0;
}
/**
* Scan the array for objects which have become finalizable and move
* them to the Finalizable class
*
* @param trace The trace instance to use.
*/
public static int moveToFinalizable(TraceLocal trace) {
int cursor = 0;
int newFinalizeCount = 0;
while (cursor < candidateEnd) {
Address cand = candidate.get(cursor);
boolean isFinalizable = trace.readyToFinalize(cand.toObjectReference());
if (isFinalizable) { // object died, enqueue for finalization
candidate.set(cursor, Address.zero());
addLive(trace.retainForFinalize(cand.toObjectReference()));
newFinalizeCount++;
} else { // live beforehand but possibly moved
candidate.set(cursor, trace.getForwardedFinalizable(cand.toObjectReference()).toAddress());
}
cursor++;
}
compactCandidates();
return newFinalizeCount;
} // moveToFinalizable
/**
* Scan the array for objects which have become finalizable and move
* them to the Finalizable class
*
* @param trace The trace object to use for forwarding.
*/
@Inline
public static void forward(TraceLocal trace) {
int cursor = 0;
while (cursor < candidateEnd) {
Address cand = candidate.get(cursor);
ObjectReference newCandidate = trace.getForwardedFinalizable(cand.toObjectReference());
candidate.set(cursor, newCandidate.toAddress());
cursor++;
}
}
// methods for statistics and debugging
static int countHasFinalizer() {
return candidateEnd;
}
public static int countToBeFinalized() {
return liveEnd - liveStart;
}
}