1087 lines
44 KiB
C#
1087 lines
44 KiB
C#
|
/*******************************************************************/
|
||
|
/* WARNING */
|
||
|
/* This file should be identical in the Bartok and Singularity */
|
||
|
/* depots. Master copy resides in Bartok Depot. Changes should be */
|
||
|
/* made to Bartok Depot and propagated to Singularity Depot. */
|
||
|
/*******************************************************************/
|
||
|
|
||
|
//
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
//
|
||
|
|
||
|
#define OVERLAPPING_PHASES
|
||
|
|
||
|
namespace System.GCs {
|
||
|
|
||
|
using Microsoft.Bartok.Runtime;
|
||
|
using System.Runtime.CompilerServices;
|
||
|
using System.Threading;
|
||
|
|
||
|
#if SINGULARITY
|
||
|
using Microsoft.Singularity;
|
||
|
#if SINGULARITY_PROCESS
|
||
|
using Microsoft.Singularity.V1.Services; // Used for timing, only
|
||
|
#elif SINGULARITY_KERNEL
|
||
|
using Microsoft.Singularity.Scheduling;
|
||
|
using Microsoft.Singularity.X86;
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
/// <summary>
|
||
|
/// This class implements a semi-concurrent version of MarkSweep.
|
||
|
///
|
||
|
/// The goal for this collector is to perform as much of the processing
|
||
|
/// as possible during mutator time, and also have a fixed upper bound
|
||
|
/// on pause times.
|
||
|
///
|
||
|
/// Additionally, the collector will be required in the future to
|
||
|
/// communicate with the scheduler to prevent real time applications
|
||
|
/// from competing with the GC.
|
||
|
///
|
||
|
///
|
||
|
/// BUGBUG: NEED TO REPLICATE THE ALLOCATION COLOR LOGIC TO THE INLINED
|
||
|
/// ALLOCATION SEQUENCE. [T-DANIF 12/3/2004]
|
||
|
/// </summary>
|
||
|
|
||
|
[NoCCtor]
|
||
|
[RequiredByBartok]
|
||
|
internal class ConcurrentMSCollector: BaseCollector
|
||
|
{
|
||
|
|
||
|
internal enum MarkingPhase {
|
||
|
Dummy, // Not used!
|
||
|
Idle, // No marking is occurring
|
||
|
Requested, // No marking, but will be soon
|
||
|
ComputingRoots, // Marking the roots
|
||
|
Tracing, // Tracing gray objects
|
||
|
}
|
||
|
|
||
|
internal enum ReclamationPhase {
|
||
|
Dummy, // Not used!
|
||
|
Idle, // No reclamation is in progress
|
||
|
Reclaiming, // Reclamation is in progress
|
||
|
}
|
||
|
|
||
|
internal static MarkingPhase CurrentMarkingPhase {
|
||
|
get { return (MarkingPhase) currentMarkingPhase; }
|
||
|
set { currentMarkingPhase = (int) value; }
|
||
|
}
|
||
|
|
||
|
internal static int currentMarkingPhase;
|
||
|
|
||
|
internal static ReclamationPhase CurrentReclamationPhase;
|
||
|
|
||
|
#if SINGULARITY
|
||
|
private static TimeSpan infiniteWait;
|
||
|
#else
|
||
|
private static int infiniteWait;
|
||
|
#endif
|
||
|
|
||
|
/// <summary>
|
||
|
/// Current mark state. This is flipped between collections.
|
||
|
/// </summary>
|
||
|
internal static UIntPtr markedColor;
|
||
|
internal static UIntPtr unmarkedColor;
|
||
|
internal static UIntPtr reclamationColor;
|
||
|
|
||
|
private const uint markOne = 1U;
|
||
|
private const uint markTwo = 2U;
|
||
|
private const uint markThree = 3U;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Amount of memory to allocate between collections.
|
||
|
/// BUGBUG: This heuristic is not very good for CMS.
|
||
|
/// </summary>
|
||
|
private static UIntPtr collectionTrigger;
|
||
|
|
||
|
internal static ConcurrentMSCollector instance;
|
||
|
|
||
|
// Pointer visitors used for marking, etc
|
||
|
private static MarkReferenceVisitor markReferenceVisitor;
|
||
|
private static UpdateReferenceVisitor updateReferenceVisitor;
|
||
|
private static StackMarkReferenceVisitor stackMarkReferenceVisitor;
|
||
|
|
||
|
// Object visitors used for sweeping, etc
|
||
|
private static SweepVisitor sweepVisitor;
|
||
|
|
||
|
// Which threads are in what marking stage?
|
||
|
private static UIntPtr[] threadColor;
|
||
|
|
||
|
// Threads waiting for a particular collection to finish
|
||
|
private static int[] waitingThreads;
|
||
|
private static int firstWaitingThread;
|
||
|
private const int waitingThreadsNullValue = -1;
|
||
|
private const int waitingThreadsUnusedValue = -2;
|
||
|
|
||
|
private const uint noColor = 0xffffffff;
|
||
|
|
||
|
// The threads that performs the collection work
|
||
|
private static Thread markThread;
|
||
|
private static Thread workerThread1;
|
||
|
private static Thread workerThread2;
|
||
|
|
||
|
private static AutoResetEvent sweepPhaseMutex;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Does this collector compute a root set with threads running?
|
||
|
/// </summary>
|
||
|
internal override bool IsOnTheFlyCollector {
|
||
|
get {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Initialise the collector and allow allocations to commence.
|
||
|
/// </summary>
|
||
|
[PreInitRefCounts]
|
||
|
public static void Initialize() {
|
||
|
GC.Initialize();
|
||
|
SegregatedFreeList.Initialize();
|
||
|
unmarkedColor = (UIntPtr) markOne;
|
||
|
markedColor = (UIntPtr) markTwo;
|
||
|
reclamationColor = (UIntPtr) markThree;
|
||
|
// instance = new ConcurrentMSCollector();
|
||
|
ConcurrentMSCollector.instance = (ConcurrentMSCollector)
|
||
|
BootstrapMemory.Allocate(typeof(ConcurrentMSCollector));
|
||
|
// markReferenceVisitor = new MarkReferenceVisitor();
|
||
|
markReferenceVisitor = (MarkReferenceVisitor)
|
||
|
BootstrapMemory.Allocate(typeof(MarkReferenceVisitor));
|
||
|
// updateReferenceVisitor = new UpdateReferenceVisitor();
|
||
|
updateReferenceVisitor = (UpdateReferenceVisitor)
|
||
|
BootstrapMemory.Allocate(typeof(UpdateReferenceVisitor));
|
||
|
// stackMarkReferenceVisitor = new StackMarkReferenceVisitor();
|
||
|
stackMarkReferenceVisitor = (StackMarkReferenceVisitor)
|
||
|
BootstrapMemory.Allocate(typeof(StackMarkReferenceVisitor));
|
||
|
// sweepVisitor = new SweepVisitor();
|
||
|
sweepVisitor = (SweepVisitor)
|
||
|
BootstrapMemory.Allocate(typeof(SweepVisitor));
|
||
|
// threadColor = new UIntPtr[Thread.maxThreads];
|
||
|
threadColor = (UIntPtr[])
|
||
|
BootstrapMemory.Allocate(typeof(UIntPtr[]), Thread.maxThreads);
|
||
|
for (int i = 0; i < threadColor.Length; i++) {
|
||
|
threadColor[i] = (UIntPtr) noColor;
|
||
|
}
|
||
|
collectionTrigger = (UIntPtr) (1 << 24);
|
||
|
CurrentMarkingPhase = MarkingPhase.Idle;
|
||
|
CurrentReclamationPhase = ReclamationPhase.Idle;
|
||
|
#if SINGULARITY
|
||
|
infiniteWait = TimeSpan.Infinite;
|
||
|
#else
|
||
|
infiniteWait = Timeout.Infinite;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Perform a collection. Depending on the current phase of collection
|
||
|
/// this method will either:
|
||
|
///
|
||
|
/// 1. Start a new collection and schedule the mark thread
|
||
|
/// 2. Notice that a collection is underway and exit
|
||
|
/// 3. Clean up after a collection
|
||
|
///
|
||
|
/// BUGBUG: The interaction between the collector needs work!
|
||
|
/// </summary>
|
||
|
internal override void Collect(int currentThreadIndex,
|
||
|
int generation)
|
||
|
{
|
||
|
if (Transitions.HasGCRequest(currentThreadIndex)) {
|
||
|
// We are GC safe, so we may do this
|
||
|
Transitions.TakeDormantControlNoGC(currentThreadIndex);
|
||
|
if (Transitions.TakeGCControl(currentThreadIndex)) {
|
||
|
MutatorHandshake(currentThreadIndex);
|
||
|
Transitions.ReleaseGCControl(currentThreadIndex);
|
||
|
Thread.SignalGCEvent(markThread.threadIndex);
|
||
|
}
|
||
|
Transitions.TakeMutatorControlNoGC(currentThreadIndex);
|
||
|
} else {
|
||
|
if (generation >= 0) {
|
||
|
AddThreadToWaitList(currentThreadIndex);
|
||
|
}
|
||
|
AddCollectionRequest();
|
||
|
if (generation >= 0) {
|
||
|
Thread currentThread =
|
||
|
Thread.threadTable[currentThreadIndex];
|
||
|
while (ThreadIsWaiting(currentThreadIndex)) {
|
||
|
currentThread.WaitForEvent(infiniteWait);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal override void CheckForNeededGCWork(Thread currentThread) {
|
||
|
if (Transitions.HasGCRequest(currentThread.threadIndex)) {
|
||
|
GC.InvokeCollection(currentThread);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void CollectorHandshake(Thread collectorThread)
|
||
|
{
|
||
|
Transitions.MakeGCRequests(collectorThread.threadIndex);
|
||
|
// Handshake with all the (other) threads.
|
||
|
for(int i=0; i < Thread.threadTable.Length; i++) {
|
||
|
#if SINGULARITY_KERNEL
|
||
|
if (Scheduler.IsIdleThread(i)) {
|
||
|
continue;
|
||
|
}
|
||
|
#endif
|
||
|
// Is there an unscanned thread here?
|
||
|
while (Transitions.HasGCRequest(i)) {
|
||
|
if (Transitions.TakeGCControl(i)) {
|
||
|
MutatorHandshake(i);
|
||
|
Transitions.ReleaseGCControl(i);
|
||
|
Thread.SignalGCEvent(i);
|
||
|
} else if (Thread.threadTable[i] == null) {
|
||
|
// The thread must have terminated but the
|
||
|
// state hasn't yet changed to DormantState.
|
||
|
break;
|
||
|
} else {
|
||
|
Thread.WaitForGCEvent(collectorThread.threadIndex);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (CurrentMarkingPhase == MarkingPhase.ComputingRoots &&
|
||
|
!WriteBarrierCMS.InSnoopingPhase) {
|
||
|
MultiUseWord.CollectFromPreviousCollections(false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void MutatorHandshake(int threadIndex)
|
||
|
{
|
||
|
if (CurrentMarkingPhase == MarkingPhase.ComputingRoots &&
|
||
|
!WriteBarrierCMS.InSnoopingPhase) {
|
||
|
Thread thread = Thread.threadTable[threadIndex];
|
||
|
if (thread != null) {
|
||
|
ScanThreadRoots(thread);
|
||
|
MultiUseWord.CollectFromThread(thread);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void ScanThreadRoots(Thread thread) {
|
||
|
long start = CollectorStatistics.PerformanceCounter;
|
||
|
CollectorStatistics.Event(GCEvent.StackScanStart,
|
||
|
thread.threadIndex);
|
||
|
CallStack.ScanStack(thread, stackMarkReferenceVisitor,
|
||
|
stackMarkReferenceVisitor);
|
||
|
threadColor[thread.threadIndex] = markedColor;
|
||
|
// Report the pause
|
||
|
long pause =
|
||
|
CollectorStatistics.PerformanceCounter - start;
|
||
|
CollectorStatistics.Event(GCEvent.StackScanComplete,
|
||
|
pause);
|
||
|
}
|
||
|
|
||
|
private static bool terminateCollectorThreads;
|
||
|
|
||
|
private static void TraceThreadNotification() {
|
||
|
terminateCollectorThreads = true;
|
||
|
GC.Collect();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// This method is run by the collector threads.
|
||
|
/// </summary>
|
||
|
private static void CollectionLoop() {
|
||
|
Thread currentThread = Thread.CurrentThread;
|
||
|
#if SINGULARITY_PROCESS
|
||
|
currentThread.MakeServiceThread(new Thread.StopServiceNotice(TraceThreadNotification));
|
||
|
#endif
|
||
|
while (!terminateCollectorThreads) {
|
||
|
// Wait to be told to start working.
|
||
|
while (!TakeChargeOfTraceRequest()) {
|
||
|
currentThread.WaitForEvent(infiniteWait);
|
||
|
}
|
||
|
#if SINGULARITY
|
||
|
#if DEBUG
|
||
|
DebugStub.WriteLine("~~~~~ Start Concurrent Marking [data={0:x8}, pid={1:x3}]",
|
||
|
__arglist(SegregatedFreeList.TotalBytes,
|
||
|
PageTable.processTag >> 16));
|
||
|
#endif
|
||
|
int startTicks = Environment.TickCount;
|
||
|
#endif
|
||
|
markThread = currentThread;
|
||
|
advanceMarkColors();
|
||
|
// Construct the root set.
|
||
|
CollectorStatistics.Event(GCEvent.ComputeRootSet);
|
||
|
StartRootMarkingPhase();
|
||
|
WriteBarrierCMS.EnterSnoopingPhase();
|
||
|
// Start the process of recycling pages
|
||
|
SegregatedFreeList.RecycleGlobalPagesPhase1();
|
||
|
// One handshake to ensure that everyone starts snooping
|
||
|
CollectorHandshake(currentThread);
|
||
|
// Complete the process of recycling pages
|
||
|
SegregatedFreeList.RecycleGlobalPagesPhase2();
|
||
|
// Another handshake to ensure that all updates started by
|
||
|
// other threads prior to their handshake are done and
|
||
|
// snooping will affect all new updates.
|
||
|
CollectorHandshake(currentThread);
|
||
|
WriteBarrierCMS.LeaveSnoopingPhase();
|
||
|
ScanThreadRoots(currentThread);
|
||
|
// A third handshake to get the threads to process their
|
||
|
// own roots.
|
||
|
CollectorHandshake(currentThread);
|
||
|
Finalizer.PrepareCollectFinalizers();
|
||
|
Thread.VisitBootstrapData(markReferenceVisitor);
|
||
|
#if SINGULARITY_KERNEL
|
||
|
Kernel.VisitSpecialData(markReferenceVisitor);
|
||
|
#endif
|
||
|
MultiUseWord.VisitStrongRefs(markReferenceVisitor,
|
||
|
false /* Don't use shadows */);
|
||
|
#if !VC
|
||
|
TryAllManager.VisitStrongRefs(markReferenceVisitor);
|
||
|
#endif
|
||
|
StaticData.ScanStaticData(markReferenceVisitor);
|
||
|
CollectorStatistics.Event(GCEvent.RootSetComputed);
|
||
|
int waitingThreadList = StealWaitingThreadsList();
|
||
|
// We are now in the concurrent tracing phase.
|
||
|
ResetCollectorRequests();
|
||
|
StartTracingPhase();
|
||
|
CollectorStatistics.Event(GCEvent.TraceStart,
|
||
|
ReservedMemory);
|
||
|
// Trace live objects from the root set.
|
||
|
markReferenceVisitor.ProcessScheduledObjects();
|
||
|
CollectorStatistics.Event(GCEvent.TraceSpecial);
|
||
|
// Mark weak references that do not track resurrection as dead.
|
||
|
WeakReference.Process(updateReferenceVisitor, true, true);
|
||
|
// Resurrect any finalization candidates.
|
||
|
Finalizer.ResurrectCandidates(updateReferenceVisitor,
|
||
|
markReferenceVisitor, true);
|
||
|
// Complete closure from finalized objects.
|
||
|
markReferenceVisitor.ProcessScheduledObjects();
|
||
|
// Mark appropriate weak references as dead
|
||
|
WeakReference.Process(updateReferenceVisitor, true, false);
|
||
|
MultiUseWord.VisitWeakRefs(updateReferenceVisitor,
|
||
|
false /* Don't use shadows */);
|
||
|
#if !VC
|
||
|
TryAllManager.VisitWeakRefs(updateReferenceVisitor);
|
||
|
#endif
|
||
|
MultiUseWord.PostGCHook();
|
||
|
// Reset thread queues. They should all be empty.
|
||
|
markReferenceVisitor.Cleanup();
|
||
|
#if SINGULARITY
|
||
|
#if DEBUG
|
||
|
int middleTicks = Environment.TickCount;
|
||
|
DebugStub.WriteLine("~~~~~ Finish Concurrent Marking [data={0:x8}, pid={1:x3} ms={2:d6}]",
|
||
|
__arglist(SegregatedFreeList.TotalBytes,
|
||
|
PageTable.processTag >> 16,
|
||
|
middleTicks - startTicks));
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
markThread = nextWorkerThread(currentThread);
|
||
|
sweepPhaseMutex.WaitOne();
|
||
|
try {
|
||
|
reclamationColor = unmarkedColor;
|
||
|
FinishTracingPhase();
|
||
|
SatisfyCollectorRequest(); // May start another trace phase
|
||
|
// Sweep garbage objects
|
||
|
StartReclamationPhase();
|
||
|
CollectorStatistics.Event(GCEvent.SweepStart,
|
||
|
ReservedMemory);
|
||
|
Sweep();
|
||
|
// Clean up after the collection
|
||
|
CollectorStatistics.Event(GCEvent.SweepSpecial,
|
||
|
ReservedMemory);
|
||
|
Finalizer.ReleaseCollectFinalizers();
|
||
|
CollectorStatistics.Event(GCEvent.SweepPreCommit,
|
||
|
ReservedMemory);
|
||
|
// Commit accounting changes
|
||
|
CommitSweep();
|
||
|
CollectorStatistics.Event(GCEvent.CollectionComplete,
|
||
|
ReservedMemory);
|
||
|
// TODO: Determine a new collection trigger?
|
||
|
SignalWaitingThreads(waitingThreadList);
|
||
|
FinishReclamationPhase();
|
||
|
} finally {
|
||
|
sweepPhaseMutex.Set();
|
||
|
}
|
||
|
#if SINGULARITY
|
||
|
#if DEBUG
|
||
|
int endTicks = Environment.TickCount;
|
||
|
DebugStub.WriteLine("~~~~~ Finish Concurrent Reclamation [data={0:x8}, pid={1:x3} ms={2:d6}]",
|
||
|
__arglist(ReservedMemory,
|
||
|
PageTable.processTag >> 16,
|
||
|
endTicks - middleTicks));
|
||
|
#endif
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Allocate memory for a new object, potentially triggering a
|
||
|
/// collection.
|
||
|
/// </summary>
|
||
|
[Inline]
|
||
|
internal override UIntPtr AllocateObjectMemory(UIntPtr numBytes,
|
||
|
uint alignment,
|
||
|
Thread currentThread)
|
||
|
{
|
||
|
UIntPtr resultAddr =
|
||
|
SegregatedFreeList.AllocateFast(currentThread,
|
||
|
numBytes, alignment);
|
||
|
if (resultAddr == UIntPtr.Zero) {
|
||
|
resultAddr = AllocateObjectMemorySlow(numBytes, alignment,
|
||
|
currentThread);
|
||
|
}
|
||
|
return resultAddr;
|
||
|
}
|
||
|
|
||
|
[NoInline]
|
||
|
private UIntPtr AllocateObjectMemorySlow(UIntPtr numBytes,
|
||
|
uint alignment,
|
||
|
Thread currentThread)
|
||
|
{
|
||
|
if (GC.newBytesSinceGC > collectionTrigger) {
|
||
|
if (CurrentMarkingPhase == MarkingPhase.Idle ||
|
||
|
Transitions.HasGCRequest(currentThread.threadIndex)) {
|
||
|
GC.InvokeCollection(currentThread);
|
||
|
} else if (GC.newBytesSinceGC > (collectionTrigger<<1)) {
|
||
|
// Slow down the allocating thread a bit
|
||
|
Thread.Yield();
|
||
|
}
|
||
|
}
|
||
|
return SegregatedFreeList.AllocateSlow(currentThread,
|
||
|
numBytes, alignment);
|
||
|
}
|
||
|
|
||
|
[Inline]
|
||
|
protected override void CreateObject(Object obj, VTable vtable,
|
||
|
Thread currentThread)
|
||
|
{
|
||
|
// We expect the color to be assigned before the vtable field
|
||
|
// is initialized. This ensures that every real object has a
|
||
|
// valid color.
|
||
|
UIntPtr markBits = threadColor[currentThread.threadIndex];
|
||
|
ThreadHeaderQueue.SetGcMark(obj, markBits);
|
||
|
// The vtable field must be initialized before the object is
|
||
|
// inserted into a list of objects to be scanned.
|
||
|
base.CreateObject(obj, vtable, currentThread);
|
||
|
// If necessary, mark the object for future scanning
|
||
|
if (CurrentMarkingPhase == MarkingPhase.ComputingRoots &&
|
||
|
Transitions.HasGCRequest(currentThread.threadIndex)) {
|
||
|
ThreadHeaderQueue.PushUnsafe(currentThread, obj, markBits);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Return the generation for an object. We only have one
|
||
|
/// generation so we always return generation zero.
|
||
|
/// </summary>
|
||
|
internal override int GetGeneration(Object obj) {
|
||
|
Verifier.genericObjectVisitor.Visit(obj);
|
||
|
return MinGeneration;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// The maximum generation. For MarkSweep this is generation zero.
|
||
|
/// </summary>
|
||
|
internal override int MaxGeneration {
|
||
|
get { return (int)PageType.Owner0; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// The minimum generation. For MarkSweep this is generation zero.
|
||
|
/// </summary>
|
||
|
internal override int MinGeneration {
|
||
|
get { return (int)PageType.Owner0; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// This returns the total amount of memory that is allocated within
|
||
|
/// the collected heap.
|
||
|
/// </summary>
|
||
|
internal override long TotalMemory {
|
||
|
get {
|
||
|
return ReservedMemory;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static long ReservedMemory {
|
||
|
get {
|
||
|
return (long)SegregatedFreeList.TotalBytes;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal override void EnableHeap() {
|
||
|
// waitingThreads = new int[Thread.maxThreads]
|
||
|
waitingThreads = new int[Thread.maxThreads];
|
||
|
for (int i = 0; i < waitingThreads.Length; i++) {
|
||
|
waitingThreads[i] = waitingThreadsUnusedValue;
|
||
|
}
|
||
|
firstWaitingThread = -1;
|
||
|
// Construct the collector thread(s)
|
||
|
#if SINGULARITY_KERNEL
|
||
|
workerThread1 =
|
||
|
Thread.CreateThread(Process.kernelProcess,
|
||
|
new ThreadStart(CollectionLoop));
|
||
|
workerThread2 =
|
||
|
Thread.CreateThread(Process.kernelProcess,
|
||
|
new ThreadStart(CollectionLoop));
|
||
|
#else
|
||
|
workerThread1 = new Thread(new ThreadStart(CollectionLoop));
|
||
|
workerThread2 = new Thread(new ThreadStart(CollectionLoop));
|
||
|
#endif
|
||
|
workerThread1.Start();
|
||
|
workerThread2.Start();
|
||
|
markThread = workerThread1;
|
||
|
sweepPhaseMutex = new AutoResetEvent(true);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Destroy the heap. Nothing to do here.
|
||
|
/// </summary>
|
||
|
internal override void DestructHeap() {
|
||
|
// Do nothing
|
||
|
while (CurrentMarkingPhase != MarkingPhase.Idle &&
|
||
|
CurrentReclamationPhase != ReclamationPhase.Idle) {
|
||
|
Thread.Yield();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Verify the heap.
|
||
|
/// </summary>
|
||
|
internal override void VerifyHeap(bool beforeCollection) {
|
||
|
SegregatedFreeList.VisitAllObjects(VerifyVisitor.visitor);
|
||
|
Verifier.segregatedFreeListVerifier.VerifyHeap();
|
||
|
}
|
||
|
|
||
|
private static char ToHexDigit(int number, int position) {
|
||
|
int digit = (number >> (position * 4)) & 0xf;
|
||
|
return (char) (digit + ((digit <= 9) ? '0' : ('A' - 10)));
|
||
|
}
|
||
|
private static int flag; // = 0;
|
||
|
private static void DebugTrace(String text, int threadIndex) {
|
||
|
while (Interlocked.CompareExchange(ref flag, 1, 0) != 0) { }
|
||
|
VTable.DebugPrint(text+" "+
|
||
|
ToHexDigit(threadIndex, 2)+
|
||
|
ToHexDigit(threadIndex, 1)+
|
||
|
ToHexDigit(threadIndex, 0)+
|
||
|
"\n");
|
||
|
Interlocked.Exchange(ref flag, 0);
|
||
|
}
|
||
|
|
||
|
/// Routines to keep track of requests for collection work
|
||
|
|
||
|
private static int collectorStack; // 0:idle, 1:work, 2+:work+pending
|
||
|
|
||
|
private static void AddCollectionRequest() {
|
||
|
int stackHeight = Interlocked.Increment(ref collectorStack);
|
||
|
VTable.Assert(stackHeight > 0);
|
||
|
if (stackHeight == 1) {
|
||
|
MakeTraceRequest();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void ResetCollectorRequests() {
|
||
|
Interlocked.Exchange(ref collectorStack, 1);
|
||
|
}
|
||
|
|
||
|
private static void SatisfyCollectorRequest() {
|
||
|
int stackHeight = Interlocked.Decrement(ref collectorStack);
|
||
|
VTable.Assert(stackHeight >= 0);
|
||
|
if (stackHeight > 0 ) {
|
||
|
MakeTraceRequest();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Routines to control the commencement of the tracing phase
|
||
|
|
||
|
private static void MakeTraceRequest() {
|
||
|
CurrentMarkingPhase = MarkingPhase.Requested;
|
||
|
markThread.SignalEvent();
|
||
|
}
|
||
|
|
||
|
private static bool TakeChargeOfTraceRequest() {
|
||
|
return
|
||
|
(Interlocked.CompareExchange(ref currentMarkingPhase,
|
||
|
(int) MarkingPhase.ComputingRoots,
|
||
|
(int) MarkingPhase.Requested) ==
|
||
|
(int) MarkingPhase.Requested);
|
||
|
}
|
||
|
|
||
|
/// Routines to keep track of threads that must be notified when
|
||
|
/// a collection has been completed.
|
||
|
|
||
|
private static void AddThreadToWaitList(int threadIndex) {
|
||
|
int listHead = firstWaitingThread;
|
||
|
waitingThreads[threadIndex] = listHead;
|
||
|
while (Interlocked.CompareExchange(ref firstWaitingThread,
|
||
|
threadIndex, listHead) !=
|
||
|
listHead) {
|
||
|
listHead = firstWaitingThread;
|
||
|
waitingThreads[threadIndex] = listHead;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static bool ThreadIsWaiting(int threadIndex) {
|
||
|
return (waitingThreads[threadIndex] != waitingThreadsUnusedValue);
|
||
|
}
|
||
|
|
||
|
private static int StealWaitingThreadsList() {
|
||
|
return Interlocked.Exchange(ref firstWaitingThread,
|
||
|
waitingThreadsNullValue);
|
||
|
}
|
||
|
|
||
|
private static void SignalWaitingThreads(int listHead) {
|
||
|
while (listHead != waitingThreadsNullValue) {
|
||
|
int threadIndex = listHead;
|
||
|
listHead = waitingThreads[threadIndex];
|
||
|
waitingThreads[threadIndex] = waitingThreadsUnusedValue;
|
||
|
Thread.threadTable[threadIndex].SignalEvent();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Routines to keep track of what phases the collector threads are in.
|
||
|
|
||
|
private static void StartRootMarkingPhase() {
|
||
|
CurrentMarkingPhase = MarkingPhase.ComputingRoots;
|
||
|
}
|
||
|
|
||
|
private static void StartTracingPhase() {
|
||
|
CurrentMarkingPhase = MarkingPhase.Tracing;
|
||
|
}
|
||
|
|
||
|
private static void FinishTracingPhase() {
|
||
|
CurrentMarkingPhase = MarkingPhase.Idle;
|
||
|
}
|
||
|
|
||
|
private static void StartReclamationPhase() {
|
||
|
CurrentReclamationPhase = ReclamationPhase.Reclaiming;
|
||
|
}
|
||
|
|
||
|
private static void FinishReclamationPhase() {
|
||
|
CurrentReclamationPhase = ReclamationPhase.Idle;
|
||
|
}
|
||
|
|
||
|
// Routines to manage marking colors
|
||
|
|
||
|
private static void advanceMarkColors()
|
||
|
{
|
||
|
unmarkedColor = markedColor;
|
||
|
markedColor = nextColor(markedColor);
|
||
|
}
|
||
|
|
||
|
private static UIntPtr nextColor(UIntPtr originalColor)
|
||
|
{
|
||
|
switch ((uint) originalColor) {
|
||
|
case markOne: return (UIntPtr) markTwo;
|
||
|
case markTwo: return (UIntPtr) markThree;
|
||
|
case markThree: return (UIntPtr) markOne;
|
||
|
default: throw new Exception("advanceColor failure!");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static Thread nextWorkerThread(Thread currentThread) {
|
||
|
#if OVERLAPPING_PHASES
|
||
|
return ((currentThread == workerThread1) ?
|
||
|
workerThread2 :
|
||
|
workerThread1);
|
||
|
#else
|
||
|
return currentThread;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Walk the allocation structures and reclaim any free cells.
|
||
|
/// </summary>
|
||
|
private static void Sweep() {
|
||
|
SegregatedFreeList.VisitAllObjects(sweepVisitor);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Update alloc heap to account for data just freed.
|
||
|
/// </summary>
|
||
|
private static void CommitSweep() {
|
||
|
SegregatedFreeList.CommitFreedData();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Routines for updating pointers to new locations of marked objects
|
||
|
/// No objects are resurrected using this mechanism.
|
||
|
/// As this is mark sweep the value does not need to be updated.
|
||
|
/// </summary>
|
||
|
private class UpdateReferenceVisitor: NonNullReferenceVisitor
|
||
|
{
|
||
|
|
||
|
internal unsafe override void Visit(UIntPtr *loc) {
|
||
|
UIntPtr addr = *loc;
|
||
|
// Ignore pointers out of our memory area
|
||
|
if (PageTable.IsForeignAddr(addr)) {
|
||
|
return;
|
||
|
}
|
||
|
UIntPtr page = PageTable.Page(addr);
|
||
|
PageType pageType = PageTable.Type(page);
|
||
|
if (!PageTable.IsGcPage(pageType)) {
|
||
|
VTable.Assert((PageTable.IsNonGcPage(pageType) &&
|
||
|
PageTable.IsMyPage(page)) ||
|
||
|
PageTable.IsStackPage(pageType),
|
||
|
"update.visit invalid page");
|
||
|
return;
|
||
|
}
|
||
|
VTable.Assert(PageTable.IsMyPage(page));
|
||
|
Object obj = Magic.fromAddress(addr);
|
||
|
if (ThreadHeaderQueue.GcMark(obj) == unmarkedColor) {
|
||
|
// The object was not live
|
||
|
*loc = UIntPtr.Zero;
|
||
|
}
|
||
|
VTable.Assert(obj.vtable != null, "update.visit null vtable");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Select the generation to collect (always generation 0)
|
||
|
/// </summary>
|
||
|
internal override int CollectionGeneration(int genRequest) {
|
||
|
return MinGeneration;
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// This visitor is the core of the tracing functionality.
|
||
|
/// It builds up a buffer of references (the root set), and
|
||
|
/// then at a later point the tracing thread processes these
|
||
|
/// buffers.
|
||
|
/// </summary>
|
||
|
private class MarkReferenceVisitor : NonNullReferenceVisitor
|
||
|
{
|
||
|
|
||
|
/// <summary>
|
||
|
/// Visit an object reference.
|
||
|
/// </summary>
|
||
|
internal unsafe override void Visit(UIntPtr *loc) {
|
||
|
UIntPtr addr = *loc;
|
||
|
if (addr == UIntPtr.Zero) return;
|
||
|
VisitValue(addr);
|
||
|
}
|
||
|
|
||
|
|
||
|
public void VisitValue(UIntPtr addr) {
|
||
|
// Ignore pointers out of our memory area
|
||
|
if (PageTable.IsForeignAddr(addr)) {
|
||
|
return;
|
||
|
}
|
||
|
UIntPtr page = PageTable.Page(addr);
|
||
|
PageType pageType = PageTable.Type(page);
|
||
|
if (!PageTable.IsGcPage(pageType)) {
|
||
|
VTable.Assert((PageTable.IsNonGcPage(pageType) &&
|
||
|
PageTable.IsMyPage(page)) ||
|
||
|
PageTable.IsStackPage(pageType),
|
||
|
"value.visit invalid page");
|
||
|
return;
|
||
|
}
|
||
|
VTable.Assert(PageTable.IsMyPage(page));
|
||
|
Object obj = Magic.fromAddress(addr);
|
||
|
VTable.Assert(obj.vtable != null,
|
||
|
"value.visit null vtable");
|
||
|
WriteBarrierCMS.MarkObject(obj);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Process all marked objects from queues stored in
|
||
|
/// thread objects.
|
||
|
/// </summary>
|
||
|
public void ProcessScheduledObjects() {
|
||
|
Thread currentThread = Thread.CurrentThread;
|
||
|
StealWork();
|
||
|
do {
|
||
|
while (!ThreadHeaderQueue.IsEmpty(currentThread)) {
|
||
|
// Pop the next value
|
||
|
Object obj =
|
||
|
ThreadHeaderQueue.Pop(currentThread, markedColor);
|
||
|
// Visit Fields
|
||
|
this.VisitReferenceFields(obj);
|
||
|
}
|
||
|
} while (StealWork());
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Look through other threads and see if any have some values on
|
||
|
/// their queues that we can steal.
|
||
|
/// </summary>
|
||
|
private bool StealWork() {
|
||
|
Thread[] threadTable = Thread.threadTable;
|
||
|
Thread me = Thread.CurrentThread;
|
||
|
bool foundWork = false;
|
||
|
// Attempt to steal work from live threads
|
||
|
for (int i = 0; i < threadTable.Length; i++) {
|
||
|
Thread t = threadTable[i];
|
||
|
if (t != null && t != me &&
|
||
|
ThreadHeaderQueue.Steal(me, t, markedColor)) {
|
||
|
foundWork = true;
|
||
|
}
|
||
|
}
|
||
|
return foundWork;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Clean up after processing all queues. This involves calling
|
||
|
/// reset on each thread's queue.
|
||
|
/// </summary>
|
||
|
internal void Cleanup() {
|
||
|
Thread[] threadTable = Thread.threadTable;
|
||
|
for (int i = 0; i < threadTable.Length; i++) {
|
||
|
Thread t = threadTable[i];
|
||
|
if (t != null) {
|
||
|
if (!ThreadHeaderQueue.IsEmpty(t)) {
|
||
|
// The queues may contain new objects allocated
|
||
|
// during the root-set computation phase but added
|
||
|
// to the queue after the scanning has completed.
|
||
|
// This is benign and we can clear the queues.
|
||
|
do {
|
||
|
Object head =
|
||
|
ThreadHeaderQueue.Pop(t, markedColor);
|
||
|
VTable.Assert(ThreadHeaderQueue.GcMark(head) ==
|
||
|
markedColor);
|
||
|
} while (!ThreadHeaderQueue.IsEmpty(t));
|
||
|
}
|
||
|
ThreadHeaderQueue.Reset(t);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// This class maps an interior pointer back to the containing object
|
||
|
/// pointer and then passes it on to the object marking visitor.
|
||
|
/// </summary>
|
||
|
private class StackMarkReferenceVisitor : NonNullReferenceVisitor
|
||
|
{
|
||
|
|
||
|
/// <summary>
|
||
|
/// Visit an interior pointer stored in loc.
|
||
|
/// </summary>
|
||
|
internal unsafe override void Visit(UIntPtr *loc) {
|
||
|
UIntPtr addr = *loc;
|
||
|
// Ignore pointers out of our memory area
|
||
|
if (PageTable.IsForeignAddr(addr)) {
|
||
|
return;
|
||
|
}
|
||
|
UIntPtr page = PageTable.Page(addr);
|
||
|
PageType pageType = PageTable.Type(page);
|
||
|
if (!PageTable.IsGcPage(pageType)) {
|
||
|
VTable.Assert((PageTable.IsNonGcPage(pageType) &&
|
||
|
PageTable.IsMyPage(page)) ||
|
||
|
PageTable.IsStackPage(pageType) ||
|
||
|
PageTable.IsSharedPage(pageType),
|
||
|
"interior.visit invalid page");
|
||
|
return;
|
||
|
}
|
||
|
UIntPtr realPtr = SegregatedFreeList.Find(addr);
|
||
|
VTable.Assert(Magic.fromAddress(realPtr).vtable != null,
|
||
|
"interior.visit null vtable");
|
||
|
markReferenceVisitor.VisitValue(realPtr);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// This class is used to visit every small object, determine if it
|
||
|
/// is marked and free it if not.
|
||
|
/// </summary>
|
||
|
private class SweepVisitor : SegregatedFreeList.ObjectVisitor
|
||
|
{
|
||
|
|
||
|
private SegregatedFreeList.TempList tempList;
|
||
|
|
||
|
internal override void VisitSmall(Object obj, UIntPtr memAddr)
|
||
|
{
|
||
|
if (ThreadHeaderQueue.GcMark(obj) == reclamationColor) {
|
||
|
// Not marked.
|
||
|
tempList.Add(memAddr);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal override void VisitSmallPageEnd() {
|
||
|
SegregatedFreeList.FreeSmallList(ref tempList);
|
||
|
}
|
||
|
|
||
|
internal override UIntPtr VisitLarge(Object obj)
|
||
|
{
|
||
|
UIntPtr objectSize = ObjectLayout.Sizeof(obj);
|
||
|
if (ThreadHeaderQueue.GcMark(obj) == reclamationColor) {
|
||
|
// Not marked.
|
||
|
SegregatedFreeList.FreeLarge(obj);
|
||
|
}
|
||
|
return objectSize;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Find the object address for a given interior pointer.
|
||
|
/// </summary>
|
||
|
internal override UIntPtr FindObjectAddr(UIntPtr interiorPtr) {
|
||
|
return SegregatedFreeList.Find(interiorPtr);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Visit all objects in the heap with a specified visitor.
|
||
|
/// </summary>
|
||
|
internal override
|
||
|
void VisitObjects(ObjectLayout.ObjectVisitor objectVisitor,
|
||
|
UIntPtr lowAddr, UIntPtr highAddr)
|
||
|
{
|
||
|
VTable.Assert(PageTable.PageAligned(lowAddr),
|
||
|
"low not page aligned");
|
||
|
VTable.Assert(PageTable.PageAligned(highAddr),
|
||
|
"high not page aligned");
|
||
|
UIntPtr lowPage = PageTable.Page(lowAddr);
|
||
|
UIntPtr highPage = PageTable.Page(highAddr);
|
||
|
SegregatedFreeList.VisitObjects(lowPage, highPage, objectVisitor);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// A new thread has been created, set any allocator/collector state.
|
||
|
/// </summary>
|
||
|
internal override void NewThreadNotification(Thread newThread,
|
||
|
bool initial)
|
||
|
{
|
||
|
base.NewThreadNotification(newThread, initial);
|
||
|
threadColor[newThread.threadIndex] = markedColor;
|
||
|
ThreadHeaderQueue.Reset(newThread);
|
||
|
SegregatedFreeList.NewThreadNotification(newThread, initial);
|
||
|
if (CurrentMarkingPhase == MarkingPhase.ComputingRoots) {
|
||
|
Transitions.MakeGCRequest(newThread.threadIndex);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal override void DeadThreadNotification(Thread deadThread)
|
||
|
{
|
||
|
MultiUseWord.CollectFromThread(deadThread);
|
||
|
SegregatedFreeList.DeadThreadNotification(deadThread);
|
||
|
ThreadHeaderQueue.DeadThreadNotification(deadThread, markedColor);
|
||
|
threadColor[deadThread.threadIndex] = (UIntPtr) noColor;
|
||
|
base.DeadThreadNotification(deadThread);
|
||
|
}
|
||
|
|
||
|
internal override void ThreadStartNotification(int currentThreadIndex)
|
||
|
{
|
||
|
base.ThreadStartNotification(currentThreadIndex);
|
||
|
threadColor[currentThreadIndex] = markedColor;
|
||
|
if (CurrentMarkingPhase == MarkingPhase.ComputingRoots) {
|
||
|
Transitions.MakeGCRequest(currentThreadIndex);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal override void ThreadDormantGCNotification(int threadIndex) {
|
||
|
// We could scan our own stack, but instead we try to get
|
||
|
// some work done while the Trace thread scans our stack.
|
||
|
Thread.SignalGCEvent(markThread.threadIndex);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// This class is used to verify that there are no dangling pointers.
|
||
|
/// </summary>
|
||
|
private class VerifyVisitor : SegregatedFreeList.ObjectVisitor
|
||
|
{
|
||
|
|
||
|
internal static VerifyVisitor visitor = new VerifyVisitor();
|
||
|
|
||
|
internal override void VisitSmall(Object obj, UIntPtr memAddr) {
|
||
|
if (ThreadHeaderQueue.GcMark(obj) == markedColor) {
|
||
|
VerifyMarkVisitor.visitor.VisitReferenceFields(obj);
|
||
|
} else {
|
||
|
VTable.Assert(ThreadHeaderQueue.GcMark(obj) ==
|
||
|
unmarkedColor);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal override UIntPtr VisitLarge(Object obj) {
|
||
|
UIntPtr size;
|
||
|
if (ThreadHeaderQueue.GcMark(obj) == markedColor) {
|
||
|
// The object has the mark color, so it should only
|
||
|
// reference other objects with the mark color.
|
||
|
size = VerifyMarkVisitor.visitor.VisitReferenceFields(obj);
|
||
|
} else {
|
||
|
VTable.Assert(ThreadHeaderQueue.GcMark(obj) ==
|
||
|
unmarkedColor);
|
||
|
size = ObjectLayout.Sizeof(obj);
|
||
|
}
|
||
|
return size;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// This class is used to check that all the pointers within a marked
|
||
|
/// object point into other marked objects.
|
||
|
/// </summary>
|
||
|
private class VerifyMarkVisitor : NonNullReferenceVisitor
|
||
|
{
|
||
|
|
||
|
internal static VerifyMarkVisitor visitor
|
||
|
= new VerifyMarkVisitor();
|
||
|
|
||
|
internal unsafe override void Visit(UIntPtr *loc) {
|
||
|
UIntPtr addr = *loc;
|
||
|
UIntPtr page = PageTable.Page(addr);
|
||
|
if (PageTable.IsGcPage(page)) {
|
||
|
Object obj = Magic.fromAddress(addr);
|
||
|
|
||
|
VTable.Assert(ThreadHeaderQueue.GcMark(obj) == markedColor,
|
||
|
"dangling pointer!");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// This method loops through all non-null threads and asserts that
|
||
|
/// no thread has any work on its marking queue.
|
||
|
/// </summary>
|
||
|
private void VerifyEmptyQueues() {
|
||
|
Thread[] threadTable = Thread.threadTable;
|
||
|
for (int i = 0; i < threadTable.Length; i++) {
|
||
|
Thread t = threadTable[i];
|
||
|
if (t != null) {
|
||
|
VTable.Assert(ThreadHeaderQueue.IsEmpty(t),
|
||
|
"Non-empty Queue!");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// This method walks through all objects in the heap to ensure
|
||
|
/// that no objects have values in their queue header field
|
||
|
/// </summary>
|
||
|
private void VerifyQueueHeaders() {
|
||
|
SegregatedFreeList.VisitAllObjects(VerifyHeaderVisitor.visitor);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// This visitor trivially asserts that the objects queue header
|
||
|
/// field is zero.
|
||
|
/// </summary>
|
||
|
private class VerifyHeaderVisitor : SegregatedFreeList.ObjectVisitor
|
||
|
{
|
||
|
|
||
|
internal static VerifyHeaderVisitor visitor
|
||
|
= new VerifyHeaderVisitor();
|
||
|
|
||
|
/// <summary>
|
||
|
/// Visit small objects, checking queue header.
|
||
|
/// </summary>
|
||
|
internal unsafe override void VisitSmall(Object obj,
|
||
|
UIntPtr memAddr)
|
||
|
{
|
||
|
VTable.Deny(ThreadHeaderQueue.IsInQueue(obj),
|
||
|
"Object in ThreadHeaderQueue");
|
||
|
}
|
||
|
|
||
|
internal override UIntPtr VisitLarge(Object obj) {
|
||
|
VTable.Deny(ThreadHeaderQueue.IsInQueue(obj),
|
||
|
"Object in ThreadHeaderQueue");
|
||
|
return ObjectLayout.Sizeof(obj);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|