Allow allocation during a concurrent GC.
Previously, any thread performing a GC held the heap lock for the
entire GC. If the GC performed was a concurrent GC, mutator threads
that allocate during the GC would be blocked until the GC completed.
With this change, if the GC performed is a concurrent GC, the heap
lock is released while the roots are being traced. If a mutator
thread allocates an object from available storage, the allocation
proceeds. If a mutator thread attempts to allocate an object larger
than available storage, the thread will block until the GC completes.
Change-Id: I91a04179c6f583f878b685405a6fdd16b9995017
diff --git a/vm/alloc/HeapSource.c b/vm/alloc/HeapSource.c
index 544230d..86f9207 100644
--- a/vm/alloc/HeapSource.c
+++ b/vm/alloc/HeapSource.c
@@ -285,8 +285,7 @@
*
* These aren't exact, and should not be treated as such.
*/
-static inline void
-countAllocation(Heap *heap, const void *ptr, bool isObj)
+static void countAllocation(Heap *heap, const void *ptr, bool isObj)
{
HeapSource *hs;
@@ -789,28 +788,41 @@
HeapSource *hs = gHs;
Heap *heap;
void *ptr;
+ size_t allocated;
HS_BOILERPLATE();
heap = hs2heap(hs);
-
- if (heap->bytesAllocated + n <= hs->softLimit) {
- 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;
- * act as if the heap is full.
+ if (heap->bytesAllocated + n > hs->softLimit) {
+ /*
+ * This allocation would push us over the soft limit; act as
+ * if the heap is full.
*/
LOGV_HEAP("softLimit of %zd.%03zdMB hit for %zd-byte allocation\n",
- FRACTIONAL_MB(hs->softLimit), n);
- ptr = NULL;
+ FRACTIONAL_MB(hs->softLimit), n);
+ return NULL;
+ }
+ ptr = mspace_calloc(heap->msp, 1, n);
+ if (ptr == NULL) {
+ return NULL;
+ }
+ countAllocation(heap, ptr, true);
+ /*
+ * Check to see if a concurrent GC should be initiated.
+ */
+ if (gDvm.gcHeap->gcRunning || !hs->hasGcThread) {
+ /*
+ * The garbage collector thread is already running or has yet
+ * to be started. Do nothing.
+ */
+ return ptr;
+ }
+ allocated = heap->bytesAllocated - heap->prevBytesAllocated;
+ if (allocated > OCCUPANCY_THRESHOLD) {
+ /*
+ * We have exceeded the occupancy threshold. Wake up the
+ * garbage collector.
+ */
+ dvmSignalCond(&gHs->gcThreadCond);
}
return ptr;
}
@@ -1653,6 +1665,18 @@
goto out;
}
+ if (gDvm.gcHeap->gcRunning) {
+ /*
+ * The GC is concurrently tracing the heap. Release the heap
+ * lock, wait for the GC to complete, and try again.
+ */
+ dvmWaitForConcurrentGcToComplete();
+ if (externalAlloc(hs, n, false)) {
+ ret = true;
+ goto out;
+ }
+ }
+
/* The "allocation" failed. Free up some space by doing
* a full garbage collection. This may grow the heap source
* if the live set is sufficiently large.