/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "Dalvik.h"
#include "alloc/clz.h"
#include "alloc/HeapBitmap.h"
#include "alloc/HeapInternal.h"
#include "alloc/HeapSource.h"
#include "alloc/MarkSweep.h"
#include <limits.h> // for ULONG_MAX
#include <sys/mman.h> // for madvise(), mmap()
#include <cutils/ashmem.h>
#include <errno.h>
#define GC_DEBUG_PARANOID 2
#define GC_DEBUG_BASIC 1
#define GC_DEBUG_OFF 0
#define GC_DEBUG(l) (GC_DEBUG_LEVEL >= (l))
#if 1
#define GC_DEBUG_LEVEL GC_DEBUG_PARANOID
#else
#define GC_DEBUG_LEVEL GC_DEBUG_OFF
#endif
#define VERBOSE_GC 0
#define GC_LOG_TAG LOG_TAG "-gc"
#if LOG_NDEBUG
#define LOGV_GC(...) ((void)0)
#define LOGD_GC(...) ((void)0)
#else
#define LOGV_GC(...) LOG(LOG_VERBOSE, GC_LOG_TAG, __VA_ARGS__)
#define LOGD_GC(...) LOG(LOG_DEBUG, GC_LOG_TAG, __VA_ARGS__)
#endif
#if VERBOSE_GC
#define LOGVV_GC(...) LOGV_GC(__VA_ARGS__)
#else
#define LOGVV_GC(...) ((void)0)
#endif
#define LOGI_GC(...) LOG(LOG_INFO, GC_LOG_TAG, __VA_ARGS__)
#define LOGW_GC(...) LOG(LOG_WARN, GC_LOG_TAG, __VA_ARGS__)
#define LOGE_GC(...) LOG(LOG_ERROR, GC_LOG_TAG, __VA_ARGS__)
#define LOG_SCAN(...) LOGV_GC("SCAN: " __VA_ARGS__)
#define LOG_MARK(...) LOGV_GC("MARK: " __VA_ARGS__)
#define LOG_SWEEP(...) LOGV_GC("SWEEP: " __VA_ARGS__)
#define LOG_REF(...) LOGV_GC("REF: " __VA_ARGS__)
#define LOGV_SCAN(...) LOGVV_GC("SCAN: " __VA_ARGS__)
#define LOGV_MARK(...) LOGVV_GC("MARK: " __VA_ARGS__)
#define LOGV_SWEEP(...) LOGVV_GC("SWEEP: " __VA_ARGS__)
#define LOGV_REF(...) LOGVV_GC("REF: " __VA_ARGS__)
#if WITH_OBJECT_HEADERS
u2 gGeneration = 0;
static const Object *gMarkParent = NULL;
#endif
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif
#define ALIGN_UP_TO_PAGE_SIZE(p) \
(((size_t)(p) + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1))
/* Do not cast the result of this to a boolean; the only set bit
* may be > 1<<8.
*/
static inline long isMarked(const DvmHeapChunk *hc, const GcMarkContext *ctx)
__attribute__((always_inline));
static inline long isMarked(const DvmHeapChunk *hc, const GcMarkContext *ctx)
{
return dvmHeapBitmapIsObjectBitSetInList(ctx->bitmaps, ctx->numBitmaps, hc);
}
static bool
createMarkStack(GcMarkStack *stack)
{
const Object **limit;
size_t size;
int fd, err;
/* Create a stack big enough for the worst possible case,
* where the heap is perfectly full of the smallest object.
* TODO: be better about memory usage; use a smaller stack with
* overflow detection and recovery.
*/
size = dvmHeapSourceGetIdealFootprint() * sizeof(Object*) /
(sizeof(Object) + HEAP_SOURCE_CHUNK_OVERHEAD);
size = ALIGN_UP_TO_PAGE_SIZE(size);
fd = ashmem_create_region("dalvik-heap-markstack", size);
if (fd < 0) {
LOGE_GC("Could not create %d-byte ashmem mark stack: %s\n",
size, strerror(errno));
return false;
}
limit = (const Object **)mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE, fd, 0);
err = errno;
close(fd);
if (limit == MAP_FAILED) {
LOGE_GC("Could not mmap %d-byte ashmem mark stack: %s\n",
size, strerror(err));
return false;
}
memset(stack, 0, sizeof(*stack));
stack->limit = limit;
stack->base = (const Object **)((uintptr_t)limit + size);
stack->top = stack->base;
return true;
}
static void
destroyMarkStack(GcMarkStack *stack)
{
munmap((char *)stack->limit,
(uintptr_t)stack->base - (uintptr_t)stack->limit);
memset(stack, 0, sizeof(*stack));
}
#define MARK_STACK_PUSH(stack, obj) \
do { \
*--(stack).top = (obj); \
} while (false)
bool
dvmHeapBeginMarkStep()
{
GcMarkContext *mc = &gDvm.gcHeap->markContext;
HeapBitmap objectBitmaps[HEAP_SOURCE_MAX_HEAP_COUNT];
size_t numBitmaps;
if (!createMarkStack(&mc->stack)) {
return false;
}
numBitmaps = dvmHeapSourceGetObjectBitmaps(objectBitmaps,
HEAP_SOURCE_MAX_HEAP_COUNT);
if (numBitmaps <= 0) {
return false;
}
/* Create mark bitmaps that cover the same ranges as the
* current object bitmaps.
*/
if (!dvmHeapBitmapInitListFromTemplates(mc->bitmaps, objectBitmaps,
numBitmaps, "mark"))
{
return false;
}
mc->numBitmaps = numBitmaps;
mc->finger = NULL;
#if WITH_OBJECT_HEADERS
gGeneration++;
#endif
return true;
}
static long setAndReturnMarkBit(GcMarkContext *ctx, const DvmHeapChunk *hc)
__attribute__((always_inline));
static long
setAndReturnMarkBit(GcMarkContext *ctx, const DvmHeapChunk *hc)
{
return dvmHeapBitmapSetAndReturnObjectBitInList(ctx->bitmaps,
ctx->numBitmaps, hc);
}
static void _markObjectNonNullCommon(const Object *obj, GcMarkContext *ctx,
bool checkFinger, bool forceStack)
__attribute__((always_inline));
static void
_markObjectNonNullCommon(const Object *obj, GcMarkContext *ctx,
bool checkFinger, bool forceStack)
{
DvmHeapChunk *hc;
assert(obj != NULL);
#if GC_DEBUG(GC_DEBUG_PARANOID)
//TODO: make sure we're locked
assert(obj != (Object *)gDvm.unlinkedJavaLangClass);
assert(dvmIsValidObject(obj));
#endif
hc = ptr2chunk(obj);
if (!setAndReturnMarkBit(ctx, hc)) {
/* This object was not previously marked.
*/
if (forceStack || (checkFinger && (void *)hc < ctx->finger)) {
/* This object will need to go on the mark stack.
*/
MARK_STACK_PUSH(ctx->stack, obj);
}
#if WITH_OBJECT_HEADERS
if (hc->scanGeneration != hc->markGeneration) {
LOGE("markObject(0x%08x): wasn't scanned last time\n", (uint)obj);
dvmAbort();
}
if (hc->markGeneration == gGeneration) {
LOGE("markObject(0x%08x): already marked this generation\n",
(uint)obj);
dvmAbort();
}
hc->oldMarkGeneration = hc->markGeneration;
hc->markGeneration = gGeneration;
hc->markFingerOld = hc->markFinger;
hc->markFinger = ctx->finger;
if (gMarkParent != NULL) {
hc->parentOld = hc->parent;
hc->parent = gMarkParent;
} else {
hc->parent = (const Object *)((uintptr_t)hc->parent | 1);
}
hc->markCount++;
#endif
#if WITH_HPROF
if (gDvm.gcHeap->hprofContext != NULL) {
hprofMarkRootObject(gDvm.gcHeap->hprofContext, obj, 0);
}
#endif
#if DVM_TRACK_HEAP_MARKING
gDvm.gcHeap->markCount++;
gDvm.gcHeap->markSize += dvmHeapSourceChunkSize((void *)hc) +
HEAP_SOURCE_CHUNK_OVERHEAD;
#endif
/* obj->clazz can be NULL if we catch an object between
* dvmMalloc() and DVM_OBJECT_INIT(). This is ok.
*/
LOGV_MARK("0x%08x %s\n", (uint)obj,
obj->clazz == NULL ? "<null class>" : obj->clazz->name);
}
}
/* Used to mark objects when recursing. Recursion is done by moving
* the finger across the bitmaps in address order and marking child
* objects. Any newly-marked objects whose addresses are lower than
* the finger won't be visited by the bitmap scan, so those objects
* need to be added to the mark stack.
*/
static void
markObjectNonNull(const Object *obj, GcMarkContext *ctx)
{
_markObjectNonNullCommon(obj, ctx, true, false);
}
#define markObject(obj, ctx) \
do { \
Object *MO_obj_ = (Object *)(obj); \
if (MO_obj_ != NULL) { \
markObjectNonNull(MO_obj_, (ctx)); \
} \
} while (false)
/* If the object hasn't already been marked, mark it and
* schedule it to be scanned for references.
*
* obj may not be NULL. The macro dvmMarkObject() should
* be used in situations where a reference may be NULL.
*
* This function may only be called when marking the root
* set. When recursing, use the internal markObject[NonNull]().
*/
void
dvmMarkObjectNonNull(const Object *obj)
{
_markObjectNonNullCommon(obj, &gDvm.gcHeap->markContext, false, false);
}
/* Mark the set of root objects.
*
* Things we need to scan:
* - System classes defined by root classloader
* - For each thread:
* - Interpreted stack, from top to "curFrame"
* - Dalvik registers (args + local vars)
* - JNI local references
* - Automatic VM local references (TrackedAlloc)
* - Associated Thread/VMThread object
* - ThreadGroups (could track & start with these instead of working
* upward from Threads)
* - Exception currently being thrown, if present
* - JNI global references
* - Interned string table
* - Primitive classes
* - Special objects
* - gDvm.outOfMemoryObj
* - Objects allocated with ALLOC_NO_GC
* - Objects pending finalization (but not yet finalized)
* - Objects in debugger object registry
*
* Don't need:
* - Native stack (for in-progress stuff in the VM)
* - The TrackedAlloc stuff watches all native VM references.
*/
void dvmHeapMarkRootSet()
{
HeapRefTable *refs;
GcHeap *gcHeap;
Object **op;
gcHeap = gDvm.gcHeap;
HPROF_SET_GC_SCAN_STATE(HPROF_ROOT_STICKY_CLASS, 0);
LOG_SCAN("root class loader\n");
dvmGcScanRootClassLoader();
LOG_SCAN("primitive classes\n");
dvmGcScanPrimitiveClasses();
/* dvmGcScanRootThreadGroups() sets a bunch of
* different scan states internally.
*/
HPROF_CLEAR_GC_SCAN_STATE();
LOG_SCAN("root thread groups\n");
dvmGcScanRootThreadGroups();
HPROF_SET_GC_SCAN_STATE(HPROF_ROOT_INTERNED_STRING, 0);
LOG_SCAN("interned strings\n");
dvmGcScanInternedStrings();
HPROF_SET_GC_SCAN_STATE(HPROF_ROOT_JNI_GLOBAL, 0);
LOG_SCAN("JNI global refs\n");
dvmGcMarkJniGlobalRefs();
HPROF_SET_GC_SCAN_STATE(HPROF_ROOT_REFERENCE_CLEANUP, 0);
LOG_SCAN("pending reference operations\n");
dvmHeapMarkLargeTableRefs(gcHeap->referenceOperations, true);
HPROF_SET_GC_SCAN_STATE(HPROF_ROOT_FINALIZING, 0);
LOG_SCAN("pending finalizations\n");
dvmHeapMarkLargeTableRefs(gcHeap->pendingFinalizationRefs, false);
HPROF_SET_GC_SCAN_STATE(HPROF_ROOT_DEBUGGER, 0);
LOG_SCAN("debugger refs\n");
dvmGcMarkDebuggerRefs();
HPROF_SET_GC_SCAN_STATE(HPROF_ROOT_VM_INTERNAL, 0);
/* Mark all ALLOC_NO_GC objects.
*/
LOG_SCAN("ALLOC_NO_GC objects\n");
refs = &gcHeap->nonCollectableRefs;
op = refs->table;
while ((uintptr_t)op < (uintptr_t)refs->nextEntry) {
dvmMarkObjectNonNull(*(op++));
}
/* Mark any special objects we have sitting around.
*/
LOG_SCAN("special objects\n");
dvmMarkObjectNonNull(gDvm.outOfMemoryObj);
dvmMarkObjectNonNull(gDvm.internalErrorObj);
dvmMarkObjectNonNull(gDvm.noClassDefFoundErrorObj);
//TODO: scan object references sitting in gDvm; use pointer begin & end
HPROF_CLEAR_GC_SCAN_STATE();
}
/*
* Nothing past this point is allowed to use dvmMarkObject*().
* Scanning/recursion must use markObject*(), which takes the
* finger into account.
*/
#define dvmMarkObjectNonNull __dont_use_dvmMarkObjectNonNull__
/* Mark all of a ClassObject's interfaces.
*/
static void markInterfaces(const ClassObject *clazz, GcMarkContext *ctx)
{
ClassObject **interfaces;
int interfaceCount;
int i;
/* Mark all interfaces.
*/
interfaces = clazz->interfaces;
interfaceCount = clazz->interfaceCount;
for (i = 0; i < interfaceCount; i++) {
markObjectNonNull((Object *)*interfaces, ctx);
interfaces++;
}
}
/* Mark all objects referred to by a ClassObject's static fields.
*/
static void scanStaticFields(const ClassObject *clazz, GcMarkContext *ctx)
{
StaticField *f;
int i;
//TODO: Optimize this with a bit vector or something
f = clazz->sfields;
for (i = 0; i < clazz->sfieldCount; i++) {
char c = f->field.signature[0];
if (c == '[' || c == 'L') {
/* It's an array or class reference.
*/
markObject((Object *)f->value.l, ctx);
}
f++;
}
}
/* Mark all objects referred to by a DataObject's instance fields.
*/
static void scanInstanceFields(const DataObject *obj, ClassObject *clazz,
GcMarkContext *ctx)
{
if (false && clazz->refOffsets != CLASS_WALK_SUPER) {
unsigned int refOffsets = clazz->refOffsets;
while (refOffsets != 0) {
const int rshift = CLZ(refOffsets);
refOffsets &= ~(CLASS_HIGH_BIT >> rshift);
markObject(dvmGetFieldObject((Object*)obj,
CLASS_OFFSET_FROM_CLZ(rshift)), ctx);
}
} else {
while (clazz != NULL) {
InstField *f;
int i;
/* All of the fields that contain object references
* are guaranteed to be at the beginning of the ifields list.
*/
f = clazz->ifields;
for (i = 0; i < clazz->ifieldRefCount; i++) {
/* Mark the array or object reference.
* May be NULL.
*
* Note that, per the comment on struct InstField,
* f->byteOffset is the offset from the beginning of
* obj, not the offset into obj->instanceData.
*/
markObject(dvmGetFieldObject((Object*)obj, f->byteOffset), ctx);
f++;
}
/* This will be NULL when we hit java.lang.Object
*/
clazz = clazz->super;
}
}
}
/* Mark all objects referred to by the array's contents.
*/
static void scanObjectArray(const ArrayObject *array, GcMarkContext *ctx)
{
Object **contents;
u4 length;
u4 i;
contents = (Object **)array->contents;
length = array->length;
for (i = 0; i < length; i++) {
markObject(*contents, ctx); // may be NULL
contents++;
}
}
/* Mark all objects referred to by the ClassObject.
*/
static void scanClassObject(const ClassObject *clazz, GcMarkContext *ctx)
{
LOGV_SCAN("---------> %s\n", clazz->name);
if (IS_CLASS_FLAG_SET(clazz, CLASS_ISARRAY)) {
/* We're an array; mark the class object of the contents
* of the array.
*
* Note that we won't necessarily reach the array's element
* class by scanning the array contents; the array may be
* zero-length, or may only contain null objects.
*/
markObjectNonNull((Object *)clazz->elementClass, ctx);
}
/* We scan these explicitly in case the only remaining
* reference to a particular class object is via a data
* object; we may not be guaranteed to reach all
* live class objects via a classloader.
*/
markObject((Object *)clazz->super, ctx); // may be NULL (java.lang.Object)
markObject(clazz->classLoader, ctx); // may be NULL
scanStaticFields(clazz, ctx);
markInterfaces(clazz, ctx);
}
/* Mark all objects that obj refers to.
*
* Called on every object in markList.
*/
static void scanObject(const Object *obj, GcMarkContext *ctx)
{
ClassObject *clazz;
assert(dvmIsValidObject(obj));
LOGV_SCAN("0x%08x %s\n", (uint)obj, obj->clazz->name);
#if WITH_HPROF
if (gDvm.gcHeap->hprofContext != NULL) {
hprofDumpHeapObject(gDvm.gcHeap->hprofContext, obj);
}
#endif
#if WITH_OBJECT_HEADERS
if (ptr2chunk(obj)->scanGeneration == gGeneration) {
LOGE("object 0x%08x was already scanned this generation\n",
(uintptr_t)obj);
dvmAbort();
}
ptr2chunk(obj)->oldScanGeneration = ptr2chunk(obj)->scanGeneration;
ptr2chunk(obj)->scanGeneration = gGeneration;
ptr2chunk(obj)->scanCount++;
#endif
/* Get and mark the class object for this particular instance.
*/
clazz = obj->clazz;
if (clazz == NULL) {
/* This can happen if we catch an object between
* dvmMalloc() and DVM_OBJECT_INIT(). The object
* won't contain any references yet, so we can
* just skip it.
*/
return;
} else if (clazz == gDvm.unlinkedJavaLangClass) {
/* This class hasn't been linked yet. We're guaranteed
* that the object doesn't contain any references that
* aren't already tracked, so we can skip scanning it.
*
* NOTE: unlinkedJavaLangClass is not on the heap, so
* it's very important that we don't try marking it.
*/
return;
}
#if WITH_OBJECT_HEADERS
gMarkParent = obj;
#endif
assert(dvmIsValidObject((Object *)clazz));
markObjectNonNull((Object *)clazz, ctx);
/* Mark any references in this object.
*/
if (IS_CLASS_FLAG_SET(clazz, CLASS_ISARRAY)) {
/* It's an array object.
*/
if (IS_CLASS_FLAG_SET(clazz, CLASS_ISOBJECTARRAY)) {
/* It's an array of object references.
*/
scanObjectArray((ArrayObject *)obj, ctx);
}
// else there's nothing else to scan
} else {
/* It's a DataObject-compatible object.
*/
scanInstanceFields((DataObject *)obj, clazz, ctx);
if (IS_CLASS_FLAG_SET(clazz, CLASS_ISREFERENCE)) {
GcHeap *gcHeap = gDvm.gcHeap;
Object *referent;
/* It's a subclass of java/lang/ref/Reference.
* The fields in this class have been arranged
* such that scanInstanceFields() did not actually
* mark the "referent" field; we need to handle
* it specially.
*
* If the referent already has a strong mark (isMarked(referent)),
* we don't care about its reference status.
*/
referent = dvmGetFieldObject(obj,
gDvm.offJavaLangRefReference_referent);
if (referent != NULL &&
!isMarked(ptr2chunk(referent), &gcHeap->markContext))
{
u4 refFlags;
if (gcHeap->markAllReferents) {
LOG_REF("Hard-marking a reference\n");
/* Don't bother with normal reference-following
* behavior, just mark the referent. This should
* only be used when following objects that just
* became scheduled for finalization.
*/
markObjectNonNull(referent, ctx);
goto skip_reference;
}
/* See if this reference was handled by a previous GC.
*/
if (dvmGetFieldObject(obj,
gDvm.offJavaLangRefReference_vmData) ==
SCHEDULED_REFERENCE_MAGIC)
{
LOG_REF("Skipping scheduled reference\n");
/* Don't reschedule it, but make sure that its
* referent doesn't get collected (in case it's
* a PhantomReference and wasn't cleared automatically).
*/
//TODO: Mark these after handling all new refs of
// this strength, in case the new refs refer
// to the same referent. Not a very common
// case, though.
markObjectNonNull(referent, ctx);
goto skip_reference;
}
/* Find out what kind of reference is pointing
* to referent.
*/
refFlags = GET_CLASS_FLAG_GROUP(clazz,
CLASS_ISREFERENCE |
CLASS_ISWEAKREFERENCE |
CLASS_ISPHANTOMREFERENCE);
/* We use the vmData field of Reference objects
* as a next pointer in a singly-linked list.
* That way, we don't need to allocate any memory
* while we're doing a GC.
*/
#define ADD_REF_TO_LIST(list, ref) \
do { \
Object *ARTL_ref_ = (/*de-const*/Object *)(ref); \
dvmSetFieldObject(ARTL_ref_, \
gDvm.offJavaLangRefReference_vmData, list); \
list = ARTL_ref_; \
} while (false)
/* At this stage, we just keep track of all of
* the live references that we've seen. Later,
* we'll walk through each of these lists and
* deal with the referents.
*/
if (refFlags == CLASS_ISREFERENCE) {
/* It's a soft reference. Depending on the state,
* we'll attempt to collect all of them, some of
* them, or none of them.
*/
if (gcHeap->softReferenceCollectionState ==
SR_COLLECT_NONE)
{
sr_collect_none:
markObjectNonNull(referent, ctx);
} else if (gcHeap->softReferenceCollectionState ==
SR_COLLECT_ALL)
{
sr_collect_all:
ADD_REF_TO_LIST(gcHeap->softReferences, obj);
} else {
/* We'll only try to collect half of the
* referents.
*/
if (gcHeap->softReferenceColor++ & 1) {
goto sr_collect_none;
}
goto sr_collect_all;
}
} else {
/* It's a weak or phantom reference.
* Clearing CLASS_ISREFERENCE will reveal which.
*/
refFlags &= ~CLASS_ISREFERENCE;
if (refFlags == CLASS_ISWEAKREFERENCE) {
ADD_REF_TO_LIST(gcHeap->weakReferences, obj);
} else if (refFlags == CLASS_ISPHANTOMREFERENCE) {
ADD_REF_TO_LIST(gcHeap->phantomReferences, obj);
} else {
assert(!"Unknown reference type");
}
}
#undef ADD_REF_TO_LIST
}
}
skip_reference:
/* If this is a class object, mark various other things that
* its internals point to.
*
* All class objects are instances of java.lang.Class,
* including the java.lang.Class class object.
*/
if (clazz == gDvm.classJavaLangClass) {
scanClassObject((ClassObject *)obj, ctx);
}
}
#if WITH_OBJECT_HEADERS
gMarkParent = NULL;
#endif
}
static void
processMarkStack(GcMarkContext *ctx)
{
const Object **const base = ctx->stack.base;
/* Scan anything that's on the mark stack.
* We can't use the bitmaps anymore, so use
* a finger that points past the end of them.
*/
ctx->finger = (void *)ULONG_MAX;
while (ctx->stack.top != base) {
scanObject(*ctx->stack.top++, ctx);
}
}
#ifndef NDEBUG
static uintptr_t gLastFinger = 0;
#endif
static bool
scanBitmapCallback(size_t numPtrs, void **ptrs, const void *finger, void *arg)
{
GcMarkContext *ctx = (GcMarkContext *)arg;
size_t i;
#ifndef NDEBUG
assert((uintptr_t)finger >= gLastFinger);
gLastFinger = (uintptr_t)finger;
#endif
ctx->finger = finger;
for (i = 0; i < numPtrs; i++) {
/* The pointers we're getting back are DvmHeapChunks,
* not Objects.
*/
scanObject(chunk2ptr(*ptrs++), ctx);
}
return true;
}
/* Given bitmaps with the root set marked, find and mark all
* reachable objects. When this returns, the entire set of
* live objects will be marked and the mark stack will be empty.
*/
void dvmHeapScanMarkedObjects()
{
GcMarkContext *ctx = &gDvm.gcHeap->markContext;
assert(ctx->finger == NULL);
/* The bitmaps currently have bits set for the root set.
* Walk across the bitmaps and scan each object.
*/
#ifndef NDEBUG
gLastFinger = 0;
#endif
dvmHeapBitmapWalkList(ctx->bitmaps, ctx->numBitmaps,
scanBitmapCallback, ctx);
/* We've walked the mark bitmaps. Scan anything that's
* left on the mark stack.
*/
processMarkStack(ctx);
LOG_SCAN("done with marked objects\n");
}
/** @return true if we need to schedule a call to clear().
*/
static bool clearReference(Object *reference)
{
/* This is what the default implementation of Reference.clear()
* does. We're required to clear all references to a given
* referent atomically, so we can't pop in and out of interp
* code each time.
*
* Also, someone may have subclassed one of the basic Reference
* types, overriding clear(). We can't trust the clear()
* implementation to call super.clear(); we cannot let clear()
* resurrect the referent. If we clear it here, we can safely
* call any overriding implementations.
*/
dvmSetFieldObject(reference,
gDvm.offJavaLangRefReference_referent, NULL);
#if FANCY_REFERENCE_SUBCLASS
/* See if clear() has actually been overridden. If so,
* we need to schedule a call to it before calling enqueue().
*/
if (reference->clazz->vtable[gDvm.voffJavaLangRefReference_clear]->clazz !=
gDvm.classJavaLangRefReference)
{
/* clear() has been overridden; return true to indicate
* that we need to schedule a call to the real clear()
* implementation.
*/
return true;
}
#endif
return false;
}
/** @return true if we need to schedule a call to enqueue().
*/
static bool enqueueReference(Object *reference)
{
#if FANCY_REFERENCE_SUBCLASS
/* See if this reference class has overridden enqueue();
* if not, we can take a shortcut.
*/
if (reference->clazz->vtable[gDvm.voffJavaLangRefReference_enqueue]->clazz
== gDvm.classJavaLangRefReference)
#endif
{
Object *queue = dvmGetFieldObject(reference,
gDvm.offJavaLangRefReference_queue);
Object *queueNext = dvmGetFieldObject(reference,
gDvm.offJavaLangRefReference_queueNext);
if (queue == NULL || queueNext != NULL) {
/* There is no queue, or the reference has already
* been enqueued. The Reference.enqueue() method
* will do nothing even if we call it.
*/
return false;
}
}
/* We need to call enqueue(), but if we called it from
* here we'd probably deadlock. Schedule a call.
*/
return true;
}
/* All objects for stronger reference levels have been
* marked before this is called.
*/
void dvmHeapHandleReferences(Object *refListHead, enum RefType refType)
{
Object *reference;
GcMarkContext *markContext = &gDvm.gcHeap->markContext;
const int offVmData = gDvm.offJavaLangRefReference_vmData;
const int offReferent = gDvm.offJavaLangRefReference_referent;
bool workRequired = false;
size_t numCleared = 0;
size_t numEnqueued = 0;
reference = refListHead;
while (reference != NULL) {
Object *next;
Object *referent;
/* Pull the interesting fields out of the Reference object.
*/
next = dvmGetFieldObject(reference, offVmData);
referent = dvmGetFieldObject(reference, offReferent);
//TODO: when handling REF_PHANTOM, unlink any references
// that fail this initial if(). We need to re-walk
// the list, and it would be nice to avoid the extra
// work.
if (referent != NULL && !isMarked(ptr2chunk(referent), markContext)) {
bool schedClear, schedEnqueue;
/* This is the strongest reference that refers to referent.
* Do the right thing.
*/
switch (refType) {
case REF_SOFT:
case REF_WEAK:
schedClear = clearReference(reference);
schedEnqueue = enqueueReference(reference);
break;
case REF_PHANTOM:
/* PhantomReferences are not cleared automatically.
* Until someone clears it (or the reference itself
* is collected), the referent must remain alive.
*
* It's necessary to fully mark the referent because
* it will still be present during the next GC, and
* all objects that it points to must be valid.
* (The referent will be marked outside of this loop,
* after handing all references of this strength, in
* case multiple references point to the same object.)
*/
schedClear = false;
/* A PhantomReference is only useful with a
* queue, but since it's possible to create one
* without a queue, we need to check.
*/
schedEnqueue = enqueueReference(reference);
break;
default:
assert(!"Bad reference type");
schedClear = false;
schedEnqueue = false;
break;
}
numCleared += schedClear ? 1 : 0;
numEnqueued += schedEnqueue ? 1 : 0;
if (schedClear || schedEnqueue) {
uintptr_t workBits;
/* Stuff the clear/enqueue bits in the bottom of
* the pointer. Assumes that objects are 8-byte
* aligned.
*
* Note that we are adding the *Reference* (which
* is by definition already marked at this point) to
* this list; we're not adding the referent (which
* has already been cleared).
*/
assert(((intptr_t)reference & 3) == 0);
assert(((WORKER_CLEAR | WORKER_ENQUEUE) & ~3) == 0);
workBits = (schedClear ? WORKER_CLEAR : 0) |
(schedEnqueue ? WORKER_ENQUEUE : 0);
if (!dvmHeapAddRefToLargeTable(
&gDvm.gcHeap->referenceOperations,
(Object *)((uintptr_t)reference | workBits)))
{
LOGE_HEAP("dvmMalloc(): no room for any more "
"reference operations\n");
dvmAbort();
}
workRequired = true;
}
if (refType != REF_PHANTOM) {
/* Let later GCs know not to reschedule this reference.
*/
dvmSetFieldObject(reference, offVmData,
SCHEDULED_REFERENCE_MAGIC);
} // else this is handled later for REF_PHANTOM
} // else there was a stronger reference to the referent.
reference = next;
}
#define refType2str(r) \
((r) == REF_SOFT ? "soft" : ( \
(r) == REF_WEAK ? "weak" : ( \
(r) == REF_PHANTOM ? "phantom" : "UNKNOWN" )))
LOGD_HEAP("dvmHeapHandleReferences(): cleared %zd, enqueued %zd %s references\n", numCleared, numEnqueued, refType2str(refType));
/* Walk though the reference list again, and mark any non-clear/marked
* referents. Only PhantomReferences can have non-clear referents
* at this point.
*/
if (refType == REF_PHANTOM) {
bool scanRequired = false;
HPROF_SET_GC_SCAN_STATE(HPROF_ROOT_REFERENCE_CLEANUP, 0);
reference = refListHead;
while (reference != NULL) {
Object *next;
Object *referent;
/* Pull the interesting fields out of the Reference object.
*/
next = dvmGetFieldObject(reference, offVmData);
referent = dvmGetFieldObject(reference, offReferent);
if (referent != NULL && !isMarked(ptr2chunk(referent), markContext)) {
markObjectNonNull(referent, markContext);
scanRequired = true;
/* Let later GCs know not to reschedule this reference.
*/
dvmSetFieldObject(reference, offVmData,
SCHEDULED_REFERENCE_MAGIC);
}
reference = next;
}
HPROF_CLEAR_GC_SCAN_STATE();
if (scanRequired) {
processMarkStack(markContext);
}
}
if (workRequired) {
dvmSignalHeapWorker(false);
}
}
/* Find unreachable objects that need to be finalized,
* and schedule them for finalization.
*/
void dvmHeapScheduleFinalizations()
{
HeapRefTable newPendingRefs;
LargeHeapRefTable *finRefs = gDvm.gcHeap->finalizableRefs;
Object **ref;
Object **lastRef;
size_t totalPendCount;
GcMarkContext *markContext = &gDvm.gcHeap->markContext;
/*
* All reachable objects have been marked.
* Any unmarked finalizable objects need to be finalized.
*/
/* Create a table that the new pending refs will
* be added to.
*/
if (!dvmHeapInitHeapRefTable(&newPendingRefs, 128)) {
//TODO: mark all finalizable refs and hope that
// we can schedule them next time. Watch out,
// because we may be expecting to free up space
// by calling finalizers.
LOGE_GC("dvmHeapScheduleFinalizations(): no room for "
"pending finalizations\n");
dvmAbort();
}
/* Walk through finalizableRefs and move any unmarked references
* to the list of new pending refs.
*/
totalPendCount = 0;
while (finRefs != NULL) {
Object **gapRef;
size_t newPendCount = 0;
gapRef = ref = finRefs->refs.table;
lastRef = finRefs->refs.nextEntry;
while (ref < lastRef) {
DvmHeapChunk *hc;
hc = ptr2chunk(*ref);
if (!isMarked(hc, markContext)) {
if (!dvmHeapAddToHeapRefTable(&newPendingRefs, *ref)) {
//TODO: add the current table and allocate
// a new, smaller one.
LOGE_GC("dvmHeapScheduleFinalizations(): "
"no room for any more pending finalizations: %zd\n",
dvmHeapNumHeapRefTableEntries(&newPendingRefs));
dvmAbort();
}
newPendCount++;
} else {
/* This ref is marked, so will remain on finalizableRefs.
*/
if (newPendCount > 0) {
/* Copy it up to fill the holes.
*/
*gapRef++ = *ref;
} else {
/* No holes yet; don't bother copying.
*/
gapRef++;
}
}
ref++;
}
finRefs->refs.nextEntry = gapRef;
//TODO: if the table is empty when we're done, free it.
totalPendCount += newPendCount;
finRefs = finRefs->next;
}
LOGD_GC("dvmHeapScheduleFinalizations(): %zd finalizers triggered.\n",
totalPendCount);
if (totalPendCount == 0) {
/* No objects required finalization.
* Free the empty temporary table.
*/
dvmClearReferenceTable(&newPendingRefs);
return;
}
/* Add the new pending refs to the main list.
*/
if (!dvmHeapAddTableToLargeTable(&gDvm.gcHeap->pendingFinalizationRefs,
&newPendingRefs))
{
LOGE_GC("dvmHeapScheduleFinalizations(): can't insert new "
"pending finalizations\n");
dvmAbort();
}
//TODO: try compacting the main list with a memcpy loop
/* Mark the refs we just moved; we don't want them or their
* children to get swept yet.
*/
ref = newPendingRefs.table;
lastRef = newPendingRefs.nextEntry;
assert(ref < lastRef);
HPROF_SET_GC_SCAN_STATE(HPROF_ROOT_FINALIZING, 0);
while (ref < lastRef) {
markObjectNonNull(*ref, markContext);
ref++;
}
HPROF_CLEAR_GC_SCAN_STATE();
/* Set markAllReferents so that we don't collect referents whose
* only references are in final-reachable objects.
* TODO: eventually provide normal reference behavior by properly
* marking these references.
*/
gDvm.gcHeap->markAllReferents = true;
processMarkStack(markContext);
gDvm.gcHeap->markAllReferents = false;
dvmSignalHeapWorker(false);
}
void dvmHeapFinishMarkStep()
{
HeapBitmap *markBitmap;
HeapBitmap objectBitmap;
GcMarkContext *markContext;
markContext = &gDvm.gcHeap->markContext;
/* The sweep step freed every object that appeared in the
* HeapSource bitmaps that didn't appear in the mark bitmaps.
* The new state of the HeapSource is exactly the final
* mark bitmaps, so swap them in.
*
* The old bitmaps will be swapped into the context so that
* we can clean them up.
*/
dvmHeapSourceReplaceObjectBitmaps(markContext->bitmaps,
markContext->numBitmaps);
/* Clean up the old HeapSource bitmaps and anything else associated
* with the marking process.
*/
dvmHeapBitmapDeleteList(markContext->bitmaps, markContext->numBitmaps);
destroyMarkStack(&markContext->stack);
memset(markContext, 0, sizeof(*markContext));
}
#if WITH_HPROF && WITH_HPROF_UNREACHABLE
static bool
hprofUnreachableBitmapCallback(size_t numPtrs, void **ptrs,
const void *finger, void *arg)
{
hprof_context_t *hctx = (hprof_context_t *)arg;
size_t i;
for (i = 0; i < numPtrs; i++) {
Object *obj;
/* The pointers we're getting back are DvmHeapChunks, not
* Objects.
*/
obj = (Object *)chunk2ptr(*ptrs++);
hprofMarkRootObject(hctx, obj, 0);
hprofDumpHeapObject(hctx, obj);
}
return true;
}
static void
hprofDumpUnmarkedObjects(const HeapBitmap markBitmaps[],
const HeapBitmap objectBitmaps[], size_t numBitmaps)
{
hprof_context_t *hctx = gDvm.gcHeap->hprofContext;
if (hctx == NULL) {
return;
}
LOGI("hprof: dumping unreachable objects\n");
HPROF_SET_GC_SCAN_STATE(HPROF_UNREACHABLE, 0);
dvmHeapBitmapXorWalkLists(markBitmaps, objectBitmaps, numBitmaps,
hprofUnreachableBitmapCallback, hctx);
HPROF_CLEAR_GC_SCAN_STATE();
}
#endif
static bool
sweepBitmapCallback(size_t numPtrs, void **ptrs, const void *finger, void *arg)
{
const ClassObject *const classJavaLangClass = gDvm.classJavaLangClass;
size_t i;
void **origPtrs = ptrs;
for (i = 0; i < numPtrs; i++) {
DvmHeapChunk *hc;
Object *obj;
/* The pointers we're getting back are DvmHeapChunks, not
* Objects.
*/
hc = (DvmHeapChunk *)*ptrs++;
obj = (Object *)chunk2ptr(hc);
#if WITH_OBJECT_HEADERS
if (hc->markGeneration == gGeneration) {
LOGE("sweeping marked object: 0x%08x\n", (uint)obj);
dvmAbort();
}
#endif
/* Free the monitor associated with the object.
*/
dvmFreeObjectMonitor(obj);
/* NOTE: Dereferencing clazz is dangerous. If obj was the last
* one to reference its class object, the class object could be
* on the sweep list, and could already have been swept, leaving
* us with a stale pointer.
*/
LOGV_SWEEP("FREE: 0x%08x %s\n", (uint)obj, obj->clazz->name);
/* This assumes that java.lang.Class will never go away.
* If it can, and we were the last reference to it, it
* could have already been swept. However, even in that case,
* gDvm.classJavaLangClass should still have a useful
* value.
*/
if (obj->clazz == classJavaLangClass) {
LOGV_SWEEP("---------------> %s\n", ((ClassObject *)obj)->name);
/* dvmFreeClassInnards() may have already been called,
* but it's safe to call on the same ClassObject twice.
*/
dvmFreeClassInnards((ClassObject *)obj);
}
#if 0
/* Overwrite the to-be-freed object to make stale references
* more obvious.
*/
{
int chunklen;
ClassObject *clazz = obj->clazz;
#if WITH_OBJECT_HEADERS
DvmHeapChunk chunk = *hc;
chunk.header = ~OBJECT_HEADER | 1;
#endif
chunklen = dvmHeapSourceChunkSize(hc);
memset(hc, 0xa5, chunklen);
obj->clazz = (ClassObject *)((uintptr_t)clazz ^ 0xffffffff);
#if WITH_OBJECT_HEADERS
*hc = chunk;
#endif
}
#endif
}
// TODO: dvmHeapSourceFreeList has a loop, just like the above
// does. Consider collapsing the two loops to save overhead.
dvmHeapSourceFreeList(numPtrs, origPtrs);
return true;
}
/* A function suitable for passing to dvmHashForeachRemove()
* to clear out any unmarked objects. Clears the low bits
* of the pointer because the intern table may set them.
*/
static int isUnmarkedObject(void *object)
{
return !isMarked(ptr2chunk((uintptr_t)object & ~(HB_OBJECT_ALIGNMENT-1)),
&gDvm.gcHeap->markContext);
}
/* Walk through the list of objects that haven't been
* marked and free them.
*/
void
dvmHeapSweepUnmarkedObjects(int *numFreed, size_t *sizeFreed)
{
const HeapBitmap *markBitmaps;
const GcMarkContext *markContext;
HeapBitmap objectBitmaps[HEAP_SOURCE_MAX_HEAP_COUNT];
size_t origObjectsAllocated;
size_t origBytesAllocated;
size_t numBitmaps;
/* All reachable objects have been marked.
* Detach any unreachable interned strings before
* we sweep.
*/
dvmGcDetachDeadInternedStrings(isUnmarkedObject);
/* Free any known objects that are not marked.
*/
origObjectsAllocated = dvmHeapSourceGetValue(HS_OBJECTS_ALLOCATED, NULL, 0);
origBytesAllocated = dvmHeapSourceGetValue(HS_BYTES_ALLOCATED, NULL, 0);
markContext = &gDvm.gcHeap->markContext;
markBitmaps = markContext->bitmaps;
numBitmaps = dvmHeapSourceGetObjectBitmaps(objectBitmaps,
HEAP_SOURCE_MAX_HEAP_COUNT);
#ifndef NDEBUG
if (numBitmaps != markContext->numBitmaps) {
LOGE("heap bitmap count mismatch: %zd != %zd\n",
numBitmaps, markContext->numBitmaps);
dvmAbort();
}
#endif
#if WITH_HPROF && WITH_HPROF_UNREACHABLE
hprofDumpUnmarkedObjects(markBitmaps, objectBitmaps, numBitmaps);
#endif
dvmHeapBitmapXorWalkLists(markBitmaps, objectBitmaps, numBitmaps,
sweepBitmapCallback, NULL);
*numFreed = origObjectsAllocated -
dvmHeapSourceGetValue(HS_OBJECTS_ALLOCATED, NULL, 0);
*sizeFreed = origBytesAllocated -
dvmHeapSourceGetValue(HS_BYTES_ALLOCATED, NULL, 0);
#ifdef WITH_PROFILER
if (gDvm.allocProf.enabled) {
gDvm.allocProf.freeCount += *numFreed;
gDvm.allocProf.freeSize += *sizeFreed;
}
#endif
}