Use the card table to scan the immune region of the heap.
Using the card table results in a dramatic cost reduction in scanning
time. During a reboot, scanning each object in the zygote for
pointers to the application heap costs an average of 20ms of thread
time on a stingray with 8 megabyte zygote. In comparison, scanning
dirty cards in the zygote costs 300us on average.
Change-Id: I1dba35646d509e6b1b4535e291a1eb6f66d7b218
diff --git a/vm/alloc/CardTable.c b/vm/alloc/CardTable.c
index a745c43..bbbb4ad 100644
--- a/vm/alloc/CardTable.c
+++ b/vm/alloc/CardTable.c
@@ -44,11 +44,7 @@
* byte is equal to GC_DIRTY_CARD. See dvmCardTableStartup for details.
*/
-/*
- * Initializes the card table; must be called before any other
- * dvmCardTable*() functions.
- */
-bool dvmCardTableStartup(size_t heapMaximumSize)
+static bool allocCardTable(size_t heapMaximumSize)
{
size_t length;
void *allocBase;
@@ -60,6 +56,7 @@
/* Set up the card table */
length = heapMaximumSize / GC_CARD_SIZE;
+ assert(length * GC_CARD_SIZE == heapMaximumSize);
/* Allocate an extra 256 bytes to allow fixed low-byte of base */
allocBase = dvmAllocRegion(length + 0x100, PROT_READ | PROT_WRITE,
"dalvik-card-table");
@@ -83,19 +80,109 @@
return true;
}
+static bool allocModUnionTable(size_t heapMaximumSize)
+{
+ size_t length = heapMaximumSize / GC_CARD_SIZE / HB_BITS_PER_WORD;
+ assert(length * GC_CARD_SIZE * HB_BITS_PER_WORD == heapMaximumSize);
+ int prot = PROT_READ | PROT_WRITE;
+ void *allocBase = dvmAllocRegion(length, prot, "dalvik-modunion-table");
+ if (allocBase == NULL) {
+ return false;
+ }
+ GcHeap *gcHeap = gDvm.gcHeap;
+ gcHeap->modUnionTableBase = (u1*)allocBase;
+ gcHeap->modUnionTableLength = length;
+ return true;
+}
+
+/*
+ * Initializes the card table; must be called before any other
+ * dvmCardTable*() functions.
+ */
+bool dvmCardTableStartup(size_t heapMaximumSize)
+{
+ return allocCardTable(heapMaximumSize) && allocModUnionTable(heapMaximumSize);
+}
+
+/*
+ * Releases storage for the card table and clears its globals.
+ */
+static void freeCardTable()
+{
+ if (gDvm.biasedCardTableBase == NULL) {
+ return;
+ }
+ gDvm.biasedCardTableBase = NULL;
+ munmap(gDvm.gcHeap->cardTableBase, gDvm.gcHeap->cardTableLength);
+ gDvm.gcHeap->cardTableBase = NULL;
+ gDvm.gcHeap->cardTableLength = 0;
+}
+
+/*
+ * Releases storage for the mod union table and clears its globals.
+ */
+static void freeModUnionTable()
+{
+ if (gDvm.gcHeap->modUnionTableBase == NULL) {
+ return;
+ }
+ munmap(gDvm.gcHeap->modUnionTableBase, gDvm.gcHeap->modUnionTableLength);
+ gDvm.gcHeap->modUnionTableBase = NULL;
+ gDvm.gcHeap->modUnionTableLength = 0;
+}
+
/*
* Tears down the entire CardTable.
*/
void dvmCardTableShutdown()
{
- gDvm.biasedCardTableBase = NULL;
- munmap(gDvm.gcHeap->cardTableBase, gDvm.gcHeap->cardTableLength);
+ freeCardTable();
+ freeModUnionTable();
+}
+
+/*
+ * Set a bit in the mod union table for each dirty byte in the card
+ * table. Clears the corresponding byte in the card table.
+ */
+static void moveCardsToModUnion(u1 *base, u1 *limit)
+{
+ GcHeap *h = gDvm.gcHeap;
+ u1 *baseCard = dvmCardFromAddr(base);
+ u1 *limitCard = dvmCardFromAddr(limit);
+ u4 *bits = (u4*)h->modUnionTableBase;
+ u1 *heapBase = (u1*)dvmHeapSourceGetBase();
+ u1 *card;
+ for (card = baseCard; card < limitCard; ++card) {
+ if (*card == GC_CARD_CLEAN) {
+ continue;
+ }
+ u1 *addr = (u1*)dvmAddrFromCard(card);
+ u1 *biased = (u1*)((uintptr_t)addr - (uintptr_t)heapBase);
+ size_t offset = (uintptr_t)biased / GC_CARD_SIZE / HB_BITS_PER_WORD;
+ u4 bit = 1 << (((uintptr_t)biased / GC_CARD_SIZE) % HB_BITS_PER_WORD);
+ assert((u1*)&bits[offset] >= h->modUnionTableBase);
+ assert((u1*)&bits[offset] < h->modUnionTableBase+h->modUnionTableLength);
+ bits[offset] |= bit;
+ *card = GC_CARD_CLEAN;
+ }
}
void dvmClearCardTable(void)
{
- assert(gDvm.gcHeap->cardTableBase != NULL);
- memset(gDvm.gcHeap->cardTableBase, GC_CARD_CLEAN, gDvm.gcHeap->cardTableLength);
+ uintptr_t base[HEAP_SOURCE_MAX_HEAP_COUNT];
+ uintptr_t limit[HEAP_SOURCE_MAX_HEAP_COUNT];
+ size_t numHeaps = dvmHeapSourceGetNumHeaps();
+ dvmHeapSourceGetRegions(base, NULL, limit, numHeaps);
+ size_t i;
+ for (i = 0; i < numHeaps; ++i) {
+ if (i != 0) {
+ moveCardsToModUnion((u1*)base[i], (u1*)limit[i]);
+ } else {
+ u1 *baseCard = dvmCardFromAddr((u1*)base[i]);
+ u1 *limitCard = dvmCardFromAddr((u1*)limit[i]);
+ memset(baseCard, GC_CARD_CLEAN, limitCard - baseCard);
+ }
+ }
}
/*
diff --git a/vm/alloc/Heap.c b/vm/alloc/Heap.c
index 6438216..c0ef8fb 100644
--- a/vm/alloc/Heap.c
+++ b/vm/alloc/Heap.c
@@ -647,6 +647,7 @@
/* Mark the set of objects that are strongly reachable from the roots.
*/
LOGD_HEAP("Marking...");
+ dvmClearCardTable();
dvmHeapMarkRootSet();
/* dvmHeapScanMarkedObjects() will build the lists of known
@@ -662,7 +663,6 @@
* heap to allow mutator threads to allocate from free space.
*/
rootEnd = dvmGetRelativeTimeMsec();
- dvmClearCardTable();
dvmUnlockHeap();
dvmResumeAllThreads(SUSPEND_FOR_GC);
}
@@ -672,7 +672,7 @@
* objects will also be marked.
*/
LOGD_HEAP("Recursing...");
- dvmHeapScanMarkedObjects();
+ dvmHeapScanMarkedObjects(spec->isPartial);
if (spec->isConcurrent) {
/*
diff --git a/vm/alloc/HeapBitmap.c b/vm/alloc/HeapBitmap.c
index 15ba15c..ffeff62 100644
--- a/vm/alloc/HeapBitmap.c
+++ b/vm/alloc/HeapBitmap.c
@@ -135,12 +135,16 @@
* visited.
*/
void dvmHeapBitmapScanWalk(HeapBitmap *bitmap,
+ uintptr_t base, uintptr_t max,
BitmapScanCallback *callback, void *arg)
{
assert(bitmap != NULL);
assert(bitmap->bits != NULL);
assert(callback != NULL);
- uintptr_t end = HB_OFFSET_TO_INDEX(bitmap->max - bitmap->base);
+ assert(base <= max);
+ assert(base >= bitmap->base);
+ assert(max <= bitmap->max);
+ uintptr_t end = HB_OFFSET_TO_INDEX(max - base);
uintptr_t i;
for (i = 0; i <= end; ++i) {
unsigned long word = bitmap->bits[i];
diff --git a/vm/alloc/HeapBitmap.h b/vm/alloc/HeapBitmap.h
index d7936ae..fecc2a9 100644
--- a/vm/alloc/HeapBitmap.h
+++ b/vm/alloc/HeapBitmap.h
@@ -113,6 +113,7 @@
* address.
*/
void dvmHeapBitmapScanWalk(HeapBitmap *bitmap,
+ uintptr_t base, uintptr_t max,
BitmapScanCallback *callback, void *arg);
/*
diff --git a/vm/alloc/HeapInternal.h b/vm/alloc/HeapInternal.h
index 119c417..d10a417 100644
--- a/vm/alloc/HeapInternal.h
+++ b/vm/alloc/HeapInternal.h
@@ -99,6 +99,10 @@
u1* cardTableBase;
size_t cardTableLength;
+ /* GC's modified union table. */
+ u1* modUnionTableBase;
+ size_t modUnionTableLength;
+
/* Is the GC running? Used to avoid recursive calls to GC.
*/
bool gcRunning;
diff --git a/vm/alloc/HeapSource.c b/vm/alloc/HeapSource.c
index 68d429f..197fb6c 100644
--- a/vm/alloc/HeapSource.c
+++ b/vm/alloc/HeapSource.c
@@ -607,6 +607,7 @@
*/
LOGV("Splitting out new zygote heap\n");
gDvm.newZygoteHeapAllocated = true;
+ dvmClearCardTable();
return addNewHeap(hs);
}
return true;
@@ -694,7 +695,8 @@
return total;
}
-void dvmHeapSourceGetRegions(uintptr_t *base, uintptr_t *max, size_t numHeaps)
+void dvmHeapSourceGetRegions(uintptr_t *base, uintptr_t *max, uintptr_t *limit,
+ size_t numHeaps)
{
HeapSource *hs = gHs;
size_t i;
@@ -704,7 +706,12 @@
assert(numHeaps <= hs->numHeaps);
for (i = 0; i < numHeaps; ++i) {
base[i] = (uintptr_t)hs->heaps[i].base;
- max[i] = MIN((uintptr_t)hs->heaps[i].limit - 1, hs->markBits.max);
+ if (max != NULL) {
+ max[i] = MIN((uintptr_t)hs->heaps[i].limit - 1, hs->markBits.max);
+ }
+ if (limit != NULL) {
+ limit[i] = (uintptr_t)hs->heaps[i].limit;
+ }
}
}
diff --git a/vm/alloc/HeapSource.h b/vm/alloc/HeapSource.h
index 61a2ce4..be6d2e5 100644
--- a/vm/alloc/HeapSource.h
+++ b/vm/alloc/HeapSource.h
@@ -68,7 +68,8 @@
* heaps. The base and max values are suitable for passing directly
* to the bitmap sweeping routine.
*/
-void dvmHeapSourceGetRegions(uintptr_t *base, uintptr_t *max, size_t numHeaps);
+void dvmHeapSourceGetRegions(uintptr_t *base, uintptr_t *max, uintptr_t *limit,
+ size_t numHeaps);
/*
* Get the bitmap representing all live objects.
diff --git a/vm/alloc/MarkSweep.c b/vm/alloc/MarkSweep.c
index ced52be..c925225 100644
--- a/vm/alloc/MarkSweep.c
+++ b/vm/alloc/MarkSweep.c
@@ -22,6 +22,7 @@
#include "alloc/HeapSource.h"
#include "alloc/MarkSweep.h"
#include "alloc/Visit.h"
+#include "alloc/VisitInlines.h"
#include <limits.h> // for ULONG_MAX
#include <sys/mman.h> // for madvise(), mmap()
#include <errno.h>
@@ -158,6 +159,170 @@
}
}
+/*
+ * Visits all objects that start on the given card.
+ */
+static void visitCard(Visitor *visitor, u1 *card, void *arg)
+{
+ assert(visitor != NULL);
+ assert(card != NULL);
+ assert(dvmIsValidCard(card));
+ u1 *addr= (u1*)dvmAddrFromCard(card);
+ u1 *limit = addr + GC_CARD_SIZE;
+ for (; addr < limit; addr += HB_OBJECT_ALIGNMENT) {
+ Object *obj = (Object *)addr;
+ GcMarkContext *ctx = &gDvm.gcHeap->markContext;
+ if (isMarked(obj, ctx)) {
+ (*visitor)(obj, arg);
+ }
+ }
+}
+
+/*
+ * Visits objects on dirty cards marked the mod union table.
+ */
+static void visitModUnionTable(Visitor *visitor, u1 *base, u1 *limit, void *arg)
+{
+ assert(visitor != NULL);
+ assert(base != NULL);
+ assert(limit != NULL);
+ assert(base <= limit);
+ u1 *heapBase = (u1*)dvmHeapSourceGetBase();
+ /* compute the start address in the bit table */
+ assert(base >= heapBase);
+ u4 *bits = (u4*)gDvm.gcHeap->modUnionTableBase;
+ /* compute the end address in the bit table */
+ size_t length = (limit - base) / GC_CARD_SIZE;
+ assert(length % sizeof(*bits) == 0);
+ length /= 4;
+ size_t i;
+ for (i = 0; i < length; ++i) {
+ if (bits[i] == 0) {
+ continue;
+ }
+ u4 word = bits[i];
+ bits[i] = 0;
+ size_t j = 0;
+ for (j = 0; j < sizeof(u4)*CHAR_BIT; ++j) {
+ if (word & (1 << j)) {
+ /* compute the base of the card */
+ size_t offset = (i*sizeof(u4)*CHAR_BIT + j) * GC_CARD_SIZE;
+ u1* addr = heapBase + offset;
+ u1* card = dvmCardFromAddr(addr);
+ /* visit all objects on the card */
+ visitCard(visitor, card, arg);
+ }
+ }
+ }
+}
+
+/*
+ * Visits objects on dirty cards marked in the card table.
+ */
+static void visitCardTable(Visitor *visitor, u1 *base, u1 *limit, void *arg)
+{
+ assert(visitor != NULL);
+ assert(base != NULL);
+ assert(limit != NULL);
+ u1 *start = dvmCardFromAddr(base);
+ u1 *end = dvmCardFromAddr(limit);
+ while (start < end) {
+ u1 *dirty = (u1 *)memchr(start, GC_CARD_DIRTY, end - start);
+ if (dirty == NULL) {
+ break;
+ }
+ assert(dirty >= start);
+ assert(dirty <= end);
+ assert(dvmIsValidCard(dirty));
+ visitCard(visitor, dirty, arg);
+ start = dirty + 1;
+ }
+}
+
+typedef struct {
+ Object *threatenBoundary;
+ Object *currObject;
+} ScanImmuneObjectContext;
+
+/*
+ * Marks the referent of an immune object it is threatened.
+ */
+static void scanImmuneObjectReferent(void *addr, void *arg)
+{
+ assert(addr != NULL);
+ assert(arg != NULL);
+ Object *obj = *(Object **)addr;
+ ScanImmuneObjectContext *ctx = (ScanImmuneObjectContext *)arg;
+ if (obj == NULL) {
+ return;
+ }
+ if (obj >= ctx->threatenBoundary) {
+ /* TODO: set a bit in the mod union table instead. */
+ dvmMarkCard(ctx->currObject);
+ markObjectNonNull(obj, &gDvm.gcHeap->markContext, false);
+ }
+}
+
+/*
+ * This function is poorly named, as is its callee.
+ */
+static void scanImmuneObject(void *addr, void *arg)
+{
+ ScanImmuneObjectContext *ctx = (ScanImmuneObjectContext *)arg;
+ Object *obj = (Object *)addr;
+ ctx->currObject = obj;
+ visitObject(scanImmuneObjectReferent, obj, arg);
+}
+
+/*
+ * Verifies that immune objects have their referents marked.
+ */
+static void verifyImmuneObjectsVisitor(void *addr, void *arg)
+{
+ assert(addr != NULL);
+ assert(arg != NULL);
+ Object *obj = *(Object **)addr;
+ GcMarkContext *ctx = (GcMarkContext *)arg;
+ if (obj == NULL || obj < (Object *)ctx->immuneLimit) {
+ return;
+ }
+ assert(dvmIsValidObject(obj));
+ if (!isMarked(obj, ctx)) {
+ LOGE("Immune reference %p points to a white threatened object %p",
+ addr, obj);
+ dvmAbort();
+ }
+}
+
+/*
+ * Visitor that searches for immune objects and verifies that all
+ * threatened referents are marked.
+ */
+static void verifyImmuneObjectsCallback(void *addr, void *arg)
+{
+ assert(addr != NULL);
+ assert(arg != NULL);
+ Object *obj = (Object *)addr;
+ GcMarkContext *ctx = (GcMarkContext *)arg;
+ if (obj->clazz == NULL) {
+ LOGI("uninitialized object @ %p (has null clazz pointer)", obj);
+ return;
+ }
+ if (obj < (Object *)ctx->immuneLimit) {
+ visitObject(verifyImmuneObjectsVisitor, obj, ctx);
+ }
+}
+
+/*
+ * Verify that immune objects refer to marked objects.
+ */
+static void verifyImmuneObjects()
+{
+ const HeapBitmap *bitmap = dvmHeapSourceGetLiveBits();
+ GcMarkContext *ctx = &gDvm.gcHeap->markContext;
+ dvmHeapBitmapWalk(bitmap, verifyImmuneObjectsCallback, ctx);
+}
+
/* Mark the set of root objects.
*
* Things we need to scan:
@@ -184,6 +349,10 @@
* - Native stack (for in-progress stuff in the VM)
* - The TrackedAlloc stuff watches all native VM references.
*/
+
+/*
+ * Blackens the root set.
+ */
void dvmHeapMarkRootSet()
{
GcHeap *gcHeap = gDvm.gcHeap;
@@ -470,6 +639,7 @@
assert(ctx != NULL);
assert(isMarked(obj, ctx));
assert(obj->clazz != NULL);
+ assert(isMarked(obj, ctx));
if (obj->clazz == gDvm.classJavaLangClass) {
scanClassObject(obj, ctx);
} else if (IS_CLASS_FLAG_SET(obj->clazz, CLASS_ISARRAY)) {
@@ -497,101 +667,31 @@
}
}
-static size_t objectSize(const Object *obj)
-{
- assert(dvmIsValidObject(obj));
- assert(dvmIsValidObject((Object *)obj->clazz));
- if (IS_CLASS_FLAG_SET(obj->clazz, CLASS_ISARRAY)) {
- return dvmArrayObjectSize((ArrayObject *)obj);
- } else if (obj->clazz == gDvm.classJavaLangClass) {
- return dvmClassObjectSize((ClassObject *)obj);
- } else {
- return obj->clazz->objectSize;
- }
-}
-
-/*
- * Scans forward to the header of the next marked object between start
- * and limit. Returns NULL if no marked objects are in that region.
- */
-static Object *nextGrayObject(const u1 *base, const u1 *limit,
- const HeapBitmap *markBits)
-{
- const u1 *ptr;
-
- assert(base < limit);
- assert(limit - base <= GC_CARD_SIZE);
- for (ptr = base; ptr < limit; ptr += HB_OBJECT_ALIGNMENT) {
- if (dvmHeapBitmapIsObjectBitSet(markBits, ptr))
- return (Object *)ptr;
- }
- return NULL;
-}
-
-/*
- * Scans range of dirty cards between start and end. A range of dirty
- * cards is composed consecutively dirty cards or dirty cards spanned
- * by a gray object. Returns the address of a clean card if the scan
- * reached a clean card or NULL if the scan reached the end.
- */
-const u1 *scanDirtyCards(const u1 *start, const u1 *end,
- GcMarkContext *ctx)
-{
- const HeapBitmap *markBits = ctx->bitmap;
- const u1 *card = start, *prevAddr = NULL;
- while (card < end) {
- if (*card != GC_CARD_DIRTY) {
- return card;
- }
- const u1 *ptr = prevAddr ? prevAddr : (u1*)dvmAddrFromCard(card);
- const u1 *limit = ptr + GC_CARD_SIZE;
- while (ptr < limit) {
- Object *obj = nextGrayObject(ptr, limit, markBits);
- if (obj == NULL) {
- break;
- }
- scanObject(obj, ctx);
- ptr = (u1*)obj + ALIGN_UP(objectSize(obj), HB_OBJECT_ALIGNMENT);
- }
- if (ptr < limit) {
- /* Ended within the current card, advance to the next card. */
- ++card;
- prevAddr = NULL;
- } else {
- /* Ended past the current card, skip ahead. */
- card = dvmCardFromAddr(ptr);
- prevAddr = ptr;
- }
- }
- return NULL;
-}
-
/*
* Blackens gray objects found on dirty cards.
*/
static void scanGrayObjects(GcMarkContext *ctx)
{
- GcHeap *h = gDvm.gcHeap;
- const u1 *base, *limit, *ptr, *dirty;
- size_t footprint;
+ HeapBitmap *bitmap = ctx->bitmap;
+ u1 *base = (u1 *)bitmap->base;
+ u1 *limit = (u1 *)ALIGN_UP(bitmap->max, GC_CARD_SIZE);
+ visitCardTable((Visitor *)scanObject, base, limit, ctx);
+}
- footprint = dvmHeapSourceGetValue(HS_FOOTPRINT, NULL, 0);
- base = &h->cardTableBase[0];
- limit = dvmCardFromAddr((u1 *)dvmHeapSourceGetBase() + footprint);
- assert(limit <= &h->cardTableBase[h->cardTableLength]);
-
- ptr = base;
- for (;;) {
- dirty = (const u1 *)memchr(ptr, GC_CARD_DIRTY, limit - ptr);
- if (dirty == NULL) {
- break;
- }
- assert((dirty > ptr) && (dirty < limit));
- ptr = scanDirtyCards(dirty, limit, ctx);
- if (ptr == NULL) {
- break;
- }
- assert((ptr > dirty) && (ptr < limit));
+/*
+ * Iterate through the immune objects and mark their referents. Uses
+ * the mod union table to save scanning time.
+ */
+void dvmHeapScanImmuneObjects(const GcMarkContext *ctx)
+{
+ ScanImmuneObjectContext ctx2;
+ memset(&ctx2, 0, sizeof(ctx2));
+ ctx2.threatenBoundary = (Object*)ctx->immuneLimit;
+ visitModUnionTable(scanImmuneObject,
+ (u1*)ctx->bitmap->base, (u1*)ctx->immuneLimit,
+ (void *)&ctx2);
+ if (gDvm.verifyCardTable) {
+ verifyImmuneObjects();
}
}
@@ -611,22 +711,32 @@
* reachable objects. When this returns, the entire set of
* live objects will be marked and the mark stack will be empty.
*/
-void dvmHeapScanMarkedObjects(void)
+void dvmHeapScanMarkedObjects(bool isPartial)
{
GcMarkContext *ctx = &gDvm.gcHeap->markContext;
+ assert(ctx != NULL);
assert(ctx->finger == NULL);
- /* The bitmaps currently have bits set for the root set.
- * Walk across the bitmaps and scan each object.
+ u1 *start;
+ if (isPartial && dvmHeapSourceGetNumHeaps() > 1) {
+ dvmHeapScanImmuneObjects(ctx);
+ start = (u1 *)ctx->immuneLimit;
+ } else {
+ start = (u1*)ctx->bitmap->base;
+ }
+ /*
+ * All objects reachable from the root set have a bit set in the
+ * mark bitmap. Walk the mark bitmap and blacken these objects.
*/
- dvmHeapBitmapScanWalk(ctx->bitmap, scanBitmapCallback, ctx);
+ dvmHeapBitmapScanWalk(ctx->bitmap,
+ (uintptr_t)start, ctx->bitmap->max,
+ scanBitmapCallback,
+ ctx);
ctx->finger = (void *)ULONG_MAX;
- /* We've walked the mark bitmaps. Scan anything that's
- * left on the mark stack.
- */
+ /* Process gray objects until the mark stack it is empty. */
processMarkStack(ctx);
}
@@ -832,7 +942,7 @@
totalPendCount += newPendCount;
finRefs = finRefs->next;
}
- LOGD("scheduleFinalizations(): %zd finalizers triggered.", totalPendCount);
+ LOGV("scheduleFinalizations(): %zd finalizers triggered.", totalPendCount);
if (totalPendCount == 0) {
/* No objects required finalization.
* Free the empty temporary table.
@@ -1003,7 +1113,7 @@
size_t i;
numHeaps = dvmHeapSourceGetNumHeaps();
- dvmHeapSourceGetRegions(base, max, numHeaps);
+ dvmHeapSourceGetRegions(base, max, NULL, numHeaps);
if (isPartial) {
assert((uintptr_t)gDvm.gcHeap->markContext.immuneLimit == base[0]);
numSweepHeaps = 1;
diff --git a/vm/alloc/MarkSweep.h b/vm/alloc/MarkSweep.h
index 9b57f45..0672aa8 100644
--- a/vm/alloc/MarkSweep.h
+++ b/vm/alloc/MarkSweep.h
@@ -49,7 +49,7 @@
bool dvmHeapBeginMarkStep(bool isPartial);
void dvmHeapMarkRootSet(void);
void dvmHeapReMarkRootSet(void);
-void dvmHeapScanMarkedObjects(void);
+void dvmHeapScanMarkedObjects(bool isPartial);
void dvmHeapReScanMarkedObjects(void);
void dvmHeapProcessReferences(Object **softReferences, bool clearSoftRefs,
Object **weakReferences,