/*
* 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.jikesrvm.memorymanagers.mminterface;
import org.jikesrvm.ArchitectureSpecific;
import org.jikesrvm.VM;
import org.jikesrvm.compilers.common.VM_CompiledMethods;
import org.jikesrvm.mm.mmtk.Collection;
import org.jikesrvm.mm.mmtk.ScanThread;
import org.jikesrvm.mm.mmtk.Scanning;
import org.jikesrvm.runtime.VM_Magic;
import org.jikesrvm.runtime.VM_Time;
import org.jikesrvm.scheduler.VM_Scheduler;
import org.jikesrvm.scheduler.VM_Synchronization;
import org.jikesrvm.scheduler.VM_Thread;
import org.jikesrvm.scheduler.greenthreads.VM_GreenProcessor;
import org.jikesrvm.scheduler.greenthreads.VM_GreenScheduler;
import org.jikesrvm.scheduler.greenthreads.VM_GreenThread;
import org.mmtk.plan.Plan;
import org.mmtk.utility.heap.HeapGrowthManager;
import org.mmtk.utility.options.Options;
import org.vmmagic.pragma.BaselineNoRegisters;
import org.vmmagic.pragma.BaselineSaveLSRegisters;
import org.vmmagic.pragma.Interruptible;
import org.vmmagic.pragma.LogicallyUninterruptible;
import org.vmmagic.pragma.NoOptCompile;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.unboxed.Address;
import org.vmmagic.unboxed.Offset;
/**
* System thread used to preform garbage collections.
*
* These threads are created by VM.boot() at runtime startup. One is created for
* each VM_Processor that will (potentially) participate in garbage collection.
*
* <pre>
* Its "run" method does the following:
* 1. wait for a collection request
* 2. synchronize with other collector threads (stop mutation)
* 3. reclaim space
* 4. synchronize with other collector threads (resume mutation)
* 5. goto 1
* </pre>
*
* Between collections, the collector threads reside on the VM_Scheduler
* collectorQueue. A collection in initiated by a call to the static
* {@link #collect()} method, which calls
* {@link VM_Handshake#requestAndAwaitCompletion()} to dequeue the collector
* threads and schedule them for execution. The collection commences when all
* scheduled collector threads arrive at the first "rendezvous" in the run
* methods run loop.
*
* An instance of VM_Handshake contains state information for the "current"
* collection. When a collection is finished, a new VM_Handshake is allocated
* for the next garbage collection.
*
* @see VM_Handshake
*/
public final class VM_CollectorThread extends VM_GreenThread {
/***********************************************************************
*
* Class variables
*/
private static final int verbose = 0;
/** Name used by toString() and when we create the associated
* java.lang.Thread. */
private static final String myName = "VM_CollectorThread";
/** When true, causes RVM collectors to display heap configuration
* at startup */
static final boolean DISPLAY_OPTIONS_AT_BOOT = false;
/**
* When true, causes RVM collectors to measure time spent in each
* phase of collection. Will also force summary statistics to be
* generated.
*/
public static final boolean TIME_GC_PHASES = false;
/**
* When true, collector threads measure time spent waiting for
* buffers while processing the Work Deque, and time spent waiting
* in Rendezvous during the collection process. Will also force
* summary statistics to be generated.
*/
public static final boolean MEASURE_WAIT_TIMES = false;
/** gc threads are indexed from 1 for now... */
public static final int GC_ORDINAL_BASE = 1;
/** array of size 1 to count arriving collector threads */
static final int[] participantCount;
/** maps processor id to assoicated collector thread */
static VM_CollectorThread[] collectorThreads;
/** number of collections */
static int collectionCount;
/**
* The VM_Handshake object that contains the state of the next or
* current (in progress) collection. Read by mutators when
* detecting a need for a collection, and passed to the collect
* method when requesting a collection.
*/
public static final VM_Handshake handshake;
/** Use by collector threads to rendezvous during collection */
public static SynchronizationBarrier gcBarrier;
/** The base collection attempt */
public static int collectionAttemptBase = 0;
/***********************************************************************
*
* Instance variables
*/
/** are we an "active participant" in gc? */
boolean isActive;
/** arrival order of collectorThreads participating in a collection */
private int gcOrdinal;
/** used by each CollectorThread when scanning stacks for references */
private final ScanThread threadScanner = new ScanThread();
/** time waiting in rendezvous (milliseconds) */
int timeInRendezvous;
static boolean gcThreadRunning;
/** The thread to use to determine stack traces if Throwables are created **/
private Address stackTraceThread;
/** @return the thread scanner instance associated with this instance */
@Uninterruptible
public ScanThread getThreadScanner() { return threadScanner; }
/***********************************************************************
*
* Initialization
*/
/**
* Class initializer. This is executed <i>prior</i> to bootstrap
* (i.e. at "build" time).
*/
static {
handshake = new VM_Handshake();
participantCount = new int[1]; // counter for threads starting a collection
}
/**
* Constructor
*
* @param stack The stack this thread will run on
* @param isActive Whether or not this thread will participate in GC
* @param processorAffinity The processor with which this thread is
* associated.
*/
VM_CollectorThread(byte[] stack, boolean isActive, VM_GreenProcessor processorAffinity) {
super(stack, myName);
makeDaemon(true); // this is redundant, but harmless
this.isActive = isActive;
this.processorAffinity = processorAffinity;
/* associate this collector thread with its affinity processor */
collectorThreads[processorAffinity.id] = this;
}
/**
* Is this the GC thread?
* @return true
*/
@Uninterruptible
public boolean isGCThread() {
return true;
}
/**
* Get the thread to use for building stack traces.
*/
@Uninterruptible
@Override
public VM_Thread getThreadForStackTrace() {
if (stackTraceThread.isZero())
return this;
return (VM_Thread)VM_Magic.addressAsObject(stackTraceThread);
}
/**
* Set the thread to use for building stack traces.
*/
@Uninterruptible
public void setThreadForStackTrace(VM_Thread thread) {
stackTraceThread = VM_Magic.objectAsAddress(thread);
}
/**
* Set the thread to use for building stack traces.
*/
@Uninterruptible
public void clearThreadForStackTrace() {
stackTraceThread = Address.zero();
}
/**
* Initialize for boot image.
*/
@Interruptible
public static void init() {
gcBarrier = new SynchronizationBarrier();
collectorThreads = new VM_CollectorThread[1 + VM_GreenScheduler.MAX_PROCESSORS];
}
/**
* Make a collector thread that will participate in gc.<p>
*
* Note: the new thread's stack must be in pinned memory: currently
* done by allocating it in immortal memory.
*
* @param processorAffinity processor to run on
* @return a new collector thread
*/
@Interruptible
public static VM_CollectorThread createActiveCollectorThread(VM_GreenProcessor processorAffinity) {
byte[] stack = MM_Interface.newStack(ArchitectureSpecific.VM_StackframeLayoutConstants.STACK_SIZE_COLLECTOR, true);
return new VM_CollectorThread(stack, true, processorAffinity);
}
/**
* Make a collector thread that will not participate in gc. It will
* serve only to lock out mutators from the current processor.
*
* Note: the new thread's stack must be in pinned memory: currently
* done by allocating it in immortal memory.
*
* @param stack stack to run on
* @param processorAffinity processor to run on
* @return a new non-particpating collector thread
*/
@Interruptible
static VM_CollectorThread createPassiveCollectorThread(byte[] stack, VM_GreenProcessor processorAffinity) {
return new VM_CollectorThread(stack, false, processorAffinity);
}
/**
* Initiate a garbage collection. Called by a mutator thread when
* its allocator runs out of space. The caller should pass the
* VM_Handshake that was referenced by the static variable "collect"
* at the time space was unavailable.
*
* @param handshake VM_Handshake for the requested collection
*/
@LogicallyUninterruptible
@Uninterruptible
public static void collect(VM_Handshake handshake, int why) {
handshake.requestAndAwaitCompletion(why);
}
/**
* Initiate a garbage collection at next GC safe point. Called by a
* mutator thread at any time. The caller should pass the
* VM_Handshake that was referenced by the static variable
* "collect".
*
* @param handshake VM_Handshake for the requested collection
*/
@Uninterruptible
public static void asyncCollect(VM_Handshake handshake, int why) {
handshake.requestAndContinue(why);
}
/**
* Override VM_Thread.toString
*
* @return A string describing this thread.
*/
@Uninterruptible
public String toString() {
return myName;
}
/**
* Returns number of collector threads participating in a collection
*
* @return The number of collector threads participating in a collection
*/
@Uninterruptible
public static int numCollectors() {
return (participantCount[0]);
}
/**
* Return the GC ordinal for this collector thread. An integer,
* 1,2,... assigned to each collector thread participating in the
* current collection. Only valid while GC is "InProgress".
*
* @return The GC ordinal
*/
@Uninterruptible
public int getGCOrdinal() {
return gcOrdinal;
}
/**
* Set the GC ordinal for this collector thread. An integer,
* 1,2,... assigned to each collector thread participating in the
* current collection.
*
* @param ord The new GC ordinal for this thread
*/
@Uninterruptible
public void setGCOrdinal(int ord) {
gcOrdinal = ord;
}
/**
* Run method for collector thread (one per VM_Processor). Enters
* an infinite loop, waiting for collections to be requested,
* performing those collections, and then waiting again. Calls
* Collection.collect to perform the collection, which will be
* different for the different allocators/collectors that the RVM
* can be configured to use.
*/
@LogicallyUninterruptible
// due to call to snipObsoleteCompiledMethods
@NoOptCompile
// refs stored in registers by opt compiler will not be relocated by GC
@BaselineNoRegisters
// refs stored in registers by baseline compiler will not be relocated by GC, so use stack only
@BaselineSaveLSRegisters
// and store all registers from previous method in prologue, so that we can stack access them while scanning this thread.
@Uninterruptible
public void run() {
for (int count = 0; ; count++) {
/* suspend this thread: it will resume when scheduled by
* VM_Handshake initiateCollection(). while suspended,
* collector threads reside on the schedulers collectorQueue */
VM_GreenScheduler.collectorMutex.lock("collector mutex");
if (verbose >= 1) VM.sysWriteln("GC Message: VM_CT.run yielding");
if (count > 0) { // resume normal scheduling
VM_GreenProcessor.getCurrentProcessor().enableThreadSwitching();
}
VM_GreenScheduler.getCurrentThread().yield(VM_GreenScheduler.collectorQueue,
VM_GreenScheduler.collectorMutex);
/* block mutators from running on the current processor */
VM_GreenProcessor.getCurrentProcessor().disableThreadSwitching("Disabled in collector to stop mutators from running on current processor");
if (verbose >= 2) VM.sysWriteln("GC Message: VM_CT.run waking up");
gcOrdinal = VM_Synchronization.fetchAndAdd(participantCount, Offset.zero(), 1) + GC_ORDINAL_BASE;
long startTime = VM_Time.nanoTime();
if (verbose > 2) VM.sysWriteln("GC Message: VM_CT.run entering first rendezvous - gcOrdinal =", gcOrdinal);
boolean userTriggered = handshake.gcTrigger == Collection.EXTERNAL_GC_TRIGGER;
boolean internalPhaseTriggered = handshake.gcTrigger == Collection.INTERNAL_PHASE_GC_TRIGGER;
if (gcOrdinal == GC_ORDINAL_BASE) {
Plan.setCollectionTrigger(handshake.gcTrigger);
}
/* wait for other collector threads to arrive or be made
* non-participants */
if (verbose >= 2) VM.sysWriteln("GC Message: VM_CT.run initializing rendezvous");
gcBarrier.startupRendezvous();
do {
/* actually perform the GC... */
if (verbose >= 2) VM.sysWriteln("GC Message: VM_CT.run starting collection");
if (isActive) Selected.Collector.get().collect(); // gc
if (verbose >= 2) VM.sysWriteln("GC Message: VM_CT.run finished collection");
gcBarrier.rendezvous(5200);
if (gcOrdinal == GC_ORDINAL_BASE) {
long elapsedTime = VM_Time.nanoTime() - startTime;
HeapGrowthManager.recordGCTime(VM_Time.nanosToMillis(elapsedTime));
if (Selected.Plan.get().lastCollectionFullHeap() && !internalPhaseTriggered) {
if (Options.variableSizeHeap.getValue() && !userTriggered) {
// Don't consider changing the heap size if gc was forced by System.gc()
HeapGrowthManager.considerHeapSize();
}
HeapGrowthManager.reset();
}
if (internalPhaseTriggered) {
if (Selected.Plan.get().lastCollectionFailed()) {
internalPhaseTriggered = false;
Plan.setCollectionTrigger(Collection.INTERNAL_GC_TRIGGER);
}
}
if (Scanning.threadStacksScanned()) {
/* Snip reference to any methods that are still marked
* obsolete after we've done stack scans. This allows
* reclaiming them on the next GC. */
VM_CompiledMethods.snipObsoleteCompiledMethods();
Scanning.clearThreadStacksScanned();
collectionAttemptBase++;
}
collectionCount += 1;
}
startTime = VM_Time.nanoTime();
gcBarrier.rendezvous(5201);
} while (Selected.Plan.get().lastCollectionFailed() && !Plan.isEmergencyCollection());
if (gcOrdinal == GC_ORDINAL_BASE && !internalPhaseTriggered) {
/* If the collection failed, we may need to throw OutOfMemory errors.
* As we have not cleared the GC flag, allocation is not budgeted.
*
* This is not flawless in the case we physically can not allocate
* anything right after a GC, but that case is unlikely (we can
* not make it happen) and is a lot of work to get around. */
if (Plan.isEmergencyCollection()) {
VM_Scheduler.getCurrentThread().setEmergencyAllocation();
boolean gcFailed = Selected.Plan.get().lastCollectionFailed();
// Allocate OOMEs (some of which *may* not get used)
for(int t=0; t <= VM_Scheduler.getThreadHighWatermark(); t++) {
VM_Thread thread = VM_Scheduler.threads[t];
if (thread != null) {
if (thread.getCollectionAttempt() > 0) {
/* this thread was allocating */
if (gcFailed || thread.physicalAllocationFailed()) {
allocateOOMEForThread(thread);
}
}
}
}
VM_Scheduler.getCurrentThread().clearEmergencyAllocation();
}
}
/* Wake up mutators waiting for this gc cycle and reset
* the handshake object to be used for next gc cycle.
* Note that mutators will not run until after thread switching
* is enabled, so no mutators can possibly arrive at old
* handshake object: it's safe to replace it with a new one. */
if (gcOrdinal == GC_ORDINAL_BASE) {
collectionAttemptBase = 0;
/* notify mutators waiting on previous handshake object -
* actually we don't notify anymore, mutators are simply in
* processor ready queues waiting to be dispatched. */
handshake.notifyCompletion();
handshake.reset();
/* schedule the VM_FinalizerThread, if there is work to do & it is idle */
Collection.scheduleFinalizerThread();
}
/* wait for other collector threads to arrive here */
rendezvous(5210);
if (verbose > 2) VM.sysWriteln("VM_CollectorThread: past rendezvous 1 after collection");
/* final cleanup for initial collector thread */
if (gcOrdinal == GC_ORDINAL_BASE) {
/* It is VERY unlikely, but possible that some RVM processors
* were found in C, and were BLOCKED_IN_NATIVE, during the
* collection, and now need to be unblocked. */
if (verbose >= 2) VM.sysWriteln("GC Message: VM_CT.run unblocking procs blocked in native during GC");
for (int i = 1; i <= VM_GreenScheduler.numProcessors; i++) {
VM_GreenProcessor vp = VM_GreenScheduler.processors[i];
if (VM.VerifyAssertions) VM._assert(vp != null);
if (vp.vpStatus == VM_GreenProcessor.BLOCKED_IN_NATIVE) {
vp.vpStatus = VM_GreenProcessor.IN_NATIVE;
if (verbose >= 2) VM.sysWriteln("GC Message: VM_CT.run unblocking RVM Processor", vp.id);
}
}
/* clear the GC flags */
Plan.collectionComplete();
gcThreadRunning = false;
} // if designated thread
rendezvous(9999);
} // end of while(true) loop
} // run
/**
* Return true if no threads are still in GC.
*
* @return <code>true</code> if no threads are still in GC.
*/
@Uninterruptible
public static boolean noThreadsInGC() {
return !gcThreadRunning;
}
@Uninterruptible
public int rendezvous(int where) {
return gcBarrier.rendezvous(where);
}
/**
* Allocate an OutOfMemoryError for a given thread.
* @param thread
*/
@LogicallyUninterruptible
@Uninterruptible
public void allocateOOMEForThread(VM_Thread thread) {
/* We are running inside a gc thread, so we will allocate if physically possible */
this.setThreadForStackTrace(thread);
thread.setOutOfMemoryError(new OutOfMemoryError());
this.clearThreadForStackTrace();
}
/*
@Uninterruptible
public static void printThreadWaitTimes() {
VM.sysWrite("*** Collector Thread Wait Times (in micro-secs)\n");
for (int i = 1; i <= VM_Scheduler.numProcessors; i++) {
VM_CollectorThread ct = VM_Magic.threadAsCollectorThread(VM_Scheduler.processors[i].activeThread );
VM.sysWrite(i);
VM.sysWrite(" SBW ");
if (ct.bufferWaitCount1 > 0)
VM.sysWrite(ct.bufferWaitCount1-1); // subtract finish wait
else
VM.sysWrite(0);
VM.sysWrite(" SBWT ");
VM.sysWrite(ct.bufferWaitTime1*1000000.0);
VM.sysWrite(" SFWT ");
VM.sysWrite(ct.finishWaitTime1*1000000.0);
VM.sysWrite(" FBW ");
if (ct.bufferWaitCount > 0)
VM.sysWrite(ct.bufferWaitCount-1); // subtract finish wait
else
VM.sysWrite(0);
VM.sysWrite(" FBWT ");
VM.sysWrite(ct.bufferWaitTime*1000000.0);
VM.sysWrite(" FFWT ");
VM.sysWrite(ct.finishWaitTime*1000000.0);
VM.sysWriteln();
}
}
*/
}