Add a mode for concurrently marking and sweeping.
When enabled, the mutator threads will be resumed while tracing
proceeds from the roots. After the trace completes the mutator
threads are suspended, the roots are remarked, and marked objects
are scanned for updates and re-marked.
There are two limitations to this implementation. For the sake
of expediency, mutators are not permitted to allocate during the
concurrent marking phase. This will be addressed in a subsequent
change. As there is no write barrier, all objects, rather than
just those objects assigned to during the concurrent phase, are
scanned for updates. This will be addressed after a write barrier
is implemented.
Change-Id: I82dba23b58a1cf985589ed21ec0cffb5ebf48aae
diff --git a/vm/alloc/HeapSource.c b/vm/alloc/HeapSource.c
index 662f99d..66249a1 100644
--- a/vm/alloc/HeapSource.c
+++ b/vm/alloc/HeapSource.c
@@ -43,6 +43,12 @@
#define HEAP_IDEAL_FREE (2 * 1024 * 1024)
#define HEAP_MIN_FREE (HEAP_IDEAL_FREE / 4)
+/*
+ * When the number of bytes allocated since the previous GC exceeds
+ * this threshold a concurrent garbage collection is triggered.
+ */
+#define OCCUPANCY_THRESHOLD (256 << 10)
+
#define HS_BOILERPLATE() \
do { \
assert(gDvm.gcHeap != NULL); \
@@ -110,6 +116,12 @@
*/
size_t bytesAllocated;
+ /*
+ * The number of bytes allocated after the previous garbage
+ * collection.
+ */
+ size_t prevBytesAllocated;
+
/* Number of objects currently allocated from this mspace.
*/
size_t objectsAllocated;
@@ -192,6 +204,15 @@
* The mark bitmap.
*/
HeapBitmap markBits;
+
+ /*
+ * State for the GC daemon.
+ */
+ bool hasGcThread;
+ pthread_t gcThread;
+ bool gcThreadShutdown;
+ pthread_mutex_t gcThreadMutex;
+ pthread_cond_t gcThreadCond;
};
#define hs2heap(hs_) (&((hs_)->heaps[0]))
@@ -282,8 +303,7 @@
assert(heap->bytesAllocated < mspace_footprint(heap->msp));
}
-static inline void
-countFree(Heap *heap, const void *ptr, bool isObj)
+static void countFree(Heap *heap, const void *ptr, bool isObj)
{
HeapSource *hs;
size_t delta;
@@ -292,6 +312,7 @@
assert(delta > 0);
if (delta < heap->bytesAllocated) {
heap->bytesAllocated -= delta;
+ heap->prevBytesAllocated = heap->bytesAllocated;
} else {
heap->bytesAllocated = 0;
}
@@ -400,6 +421,47 @@
}
/*
+ * The garbage collection daemon. Initiates a concurrent collection
+ * when signaled.
+ */
+static void *gcDaemonThread(void* arg)
+{
+ dvmChangeStatus(NULL, THREAD_VMWAIT);
+ dvmLockMutex(&gHs->gcThreadMutex);
+ while (gHs->gcThreadShutdown != true) {
+ dvmWaitCond(&gHs->gcThreadCond, &gHs->gcThreadMutex);
+ dvmLockHeap();
+ dvmChangeStatus(NULL, THREAD_RUNNING);
+ dvmCollectGarbageInternal(false, GC_CONCURRENT);
+ dvmChangeStatus(NULL, THREAD_VMWAIT);
+ dvmUnlockHeap();
+ }
+ dvmChangeStatus(NULL, THREAD_RUNNING);
+ return NULL;
+}
+
+static bool gcDaemonStartup(void)
+{
+ dvmInitMutex(&gHs->gcThreadMutex);
+ pthread_cond_init(&gHs->gcThreadCond, NULL);
+ gHs->gcThreadShutdown = false;
+ gHs->hasGcThread = dvmCreateInternalThread(&gHs->gcThread, "GC",
+ gcDaemonThread, NULL);
+ return gHs->hasGcThread;
+}
+
+static void gcDaemonShutdown(void)
+{
+ if (gDvm.concurrentMarkSweep) {
+ dvmLockMutex(&gHs->gcThreadMutex);
+ gHs->gcThreadShutdown = true;
+ dvmSignalCond(&gHs->gcThreadCond);
+ pthread_join(gHs->gcThread, NULL);
+ dvmUnlockMutex(&gHs->gcThreadMutex);
+ }
+}
+
+/*
* Initializes the heap source; must be called before any other
* dvmHeapSource*() functions. Returns a GcHeap structure
* allocated from the heap source.
@@ -472,6 +534,7 @@
hs->softLimit = INT_MAX; // no soft limit at first
hs->numHeaps = 0;
hs->sawZygote = gDvm.zygote;
+ hs->hasGcThread = false;
hs->heapBase = base;
hs->heapLength = length;
if (!addNewHeap(hs, msp, absoluteMaxSize)) {
@@ -502,6 +565,11 @@
return NULL;
}
+bool dvmHeapSourceStartupAfterZygote(void)
+{
+ return gDvm.concurrentMarkSweep ? gcDaemonStartup() : true;
+}
+
/*
* This is called while in zygote mode, right before we fork() for the
* first time. We create a heap for all future zygote process allocations,
@@ -529,6 +597,11 @@
return true;
}
+void dvmHeapSourceThreadShutdown(void)
+{
+ gcDaemonShutdown();
+}
+
/*
* Tears down the entire GcHeap structure and all of the substructures
* attached to it. This call has the side effect of setting the given
@@ -650,7 +723,7 @@
/*
* Get the bitmap representing all live objects.
*/
-HeapBitmap *dvmHeapSourceGetLiveBits()
+HeapBitmap *dvmHeapSourceGetLiveBits(void)
{
HS_BOILERPLATE();
@@ -724,6 +797,12 @@
ptr = mspace_calloc(heap->msp, 1, n);
if (ptr != NULL) {
countAllocation(heap, ptr, true);
+ size_t allocated = heap->bytesAllocated - heap->prevBytesAllocated;
+ if (allocated > OCCUPANCY_THRESHOLD) {
+ if (hs->hasGcThread == true) {
+ dvmSignalCond(&gHs->gcThreadCond);
+ }
+ }
}
} else {
/* This allocation would push us over the soft limit;