diff options
author | Carl Shapiro <cshapiro@google.com> | 2011-03-30 19:35:34 -0700 |
---|---|---|
committer | Carl Shapiro <cshapiro@google.com> | 2011-03-30 19:53:34 -0700 |
commit | ce87bfed41bbe4248b2770fb1a90f34b2518f6fa (patch) | |
tree | 8a2ca573187d322f7202df68d36a1101294f5ee5 /vm/alloc | |
parent | 6920d2c5b83f8ff0cdc8f33770cd658969dbd262 (diff) | |
download | android_dalvik-ce87bfed41bbe4248b2770fb1a90f34b2518f6fa.tar.gz android_dalvik-ce87bfed41bbe4248b2770fb1a90f34b2518f6fa.tar.bz2 android_dalvik-ce87bfed41bbe4248b2770fb1a90f34b2518f6fa.zip |
Delegate the queuing of cleared references to managed code.
Previously, the garbage collector would pass each cleared reference to
the heap worker thread for queuing. The heap worker thread would then
perform a callback into managed code for each cleared reference which
assigned the reference to its reference queue.
With this change, the garbage collector instead links together all of
the cleared references and calls back into managed code exactly once
to hand off the references for processing. This change makes the heap
worker thread and its data structures obsolete.
Change-Id: I28e02638f0877a7fd2ac96b9c3f5597a38541ebb
Diffstat (limited to 'vm/alloc')
-rw-r--r-- | vm/alloc/Alloc.c | 43 | ||||
-rw-r--r-- | vm/alloc/Alloc.h | 1 | ||||
-rw-r--r-- | vm/alloc/Heap.c | 98 | ||||
-rw-r--r-- | vm/alloc/HeapInternal.h | 32 | ||||
-rw-r--r-- | vm/alloc/HeapTable.c | 168 | ||||
-rw-r--r-- | vm/alloc/HeapTable.h | 36 | ||||
-rw-r--r-- | vm/alloc/HeapWorker.c | 472 | ||||
-rw-r--r-- | vm/alloc/HeapWorker.h | 70 | ||||
-rw-r--r-- | vm/alloc/MarkSweep.c | 54 | ||||
-rw-r--r-- | vm/alloc/MarkSweep.h | 1 | ||||
-rw-r--r-- | vm/alloc/Visit.c | 15 | ||||
-rw-r--r-- | vm/alloc/Visit.h | 2 |
12 files changed, 81 insertions, 911 deletions
diff --git a/vm/alloc/Alloc.c b/vm/alloc/Alloc.c index 6286f8db7..33f45e86f 100644 --- a/vm/alloc/Alloc.c +++ b/vm/alloc/Alloc.c @@ -20,7 +20,6 @@ #include "alloc/Heap.h" #include "alloc/HeapInternal.h" #include "alloc/HeapSource.h" -#include "alloc/HeapWorker.h" /* * Initialize the GC universe. @@ -41,9 +40,6 @@ bool dvmGcStartup(void) */ bool dvmGcStartupAfterZygote(void) { - if (!dvmHeapWorkerStartup()) { - return false; - } return dvmHeapStartupAfterZygote(); } @@ -52,7 +48,6 @@ bool dvmGcStartupAfterZygote(void) */ void dvmGcThreadShutdown(void) { - dvmHeapWorkerShutdown(); dvmHeapThreadShutdown(); } @@ -73,6 +68,44 @@ bool dvmGcPreZygoteFork(void) return dvmHeapSourceStartupBeforeFork(); } +bool dvmGcStartupClasses(void) +{ + { + const char *klassName = "Ljava/lang/ref/ReferenceQueueThread;"; + ClassObject *klass = dvmFindSystemClass(klassName); + if (klass == NULL) { + return false; + } + const char *methodName = "startReferenceQueue"; + Method *method = dvmFindDirectMethodByDescriptor(klass, methodName, "()V"); + if (method == NULL) { + return false; + } + Thread *self = dvmThreadSelf(); + assert(self != NULL); + JValue unusedResult; + dvmCallMethod(self, method, NULL, &unusedResult); + } + { + const char *klassName = "Ljava/lang/FinalizerThread;"; + ClassObject *klass = dvmFindSystemClass(klassName); + if (klass == NULL) { + return false; + } + const char *methodName = "startFinalizer"; + Method *method = dvmFindDirectMethodByDescriptor(klass, methodName, "()V"); + if (method == NULL) { + return false; + } + Thread *self = dvmThreadSelf(); + assert(self != NULL); + JValue unusedResult; + dvmCallMethod(self, method, NULL, &unusedResult); + } + + return true; +} + /* * Create a "stock instance" of an exception class. */ diff --git a/vm/alloc/Alloc.h b/vm/alloc/Alloc.h index 2dc2ae62d..aeba3f5d9 100644 --- a/vm/alloc/Alloc.h +++ b/vm/alloc/Alloc.h @@ -29,6 +29,7 @@ bool dvmCreateStockExceptions(void); bool dvmGcStartupAfterZygote(void); void dvmGcShutdown(void); void dvmGcThreadShutdown(void); +bool dvmGcStartupClasses(void); /* * Do any last-minute preparation before we call fork() for the first time. diff --git a/vm/alloc/Heap.c b/vm/alloc/Heap.c index 5d88bc395..f38eb121d 100644 --- a/vm/alloc/Heap.c +++ b/vm/alloc/Heap.c @@ -19,12 +19,10 @@ #include "Dalvik.h" #include "alloc/HeapBitmap.h" #include "alloc/Verify.h" -#include "alloc/HeapTable.h" #include "alloc/Heap.h" #include "alloc/HeapInternal.h" #include "alloc/DdmHeap.h" #include "alloc/HeapSource.h" -#include "alloc/HeapWorker.h" #include "alloc/MarkSweep.h" #include "utils/threads.h" // need Android thread priorities @@ -91,9 +89,6 @@ bool dvmHeapStartup() if (gcHeap == NULL) { return false; } - gcHeap->heapWorkerCurrentObject = NULL; - gcHeap->heapWorkerCurrentMethod = NULL; - gcHeap->heapWorkerInterpStartTime = 0LL; gcHeap->ddmHpifWhen = 0; gcHeap->ddmHpsgWhen = 0; gcHeap->ddmHpsgWhat = 0; @@ -101,22 +96,15 @@ bool dvmHeapStartup() gcHeap->ddmNhsgWhat = 0; gDvm.gcHeap = gcHeap; - /* Set up the lists and lock we'll use for finalizable - * and reference objects. + /* Set up the lists we'll use for cleared reference objects. */ - dvmInitMutex(&gDvm.heapWorkerListLock); - gcHeap->referenceOperations = NULL; + gcHeap->clearedReferences = NULL; if (!dvmCardTableStartup(gDvm.heapMaximumSize)) { LOGE_HEAP("card table startup failed."); return false; } - /* Initialize the HeapWorker locks and other state - * that the GC uses. - */ - dvmInitializeHeapWorkerState(); - return true; } @@ -130,13 +118,6 @@ void dvmHeapShutdown() //TODO: make sure we're locked if (gDvm.gcHeap != NULL) { dvmCardTableShutdown(); - /* Tables are allocated on the native heap; they need to be - * cleaned up explicitly. The process may stick around, so we - * don't want to leak any native memory. - */ - dvmHeapFreeLargeTable(gDvm.gcHeap->referenceOperations); - gDvm.gcHeap->referenceOperations = NULL; - /* Destroy the heap. Any outstanding pointers will point to * unmapped memory (unless/until someone else maps it). This * frees gDvm.gcHeap as a side-effect. @@ -177,33 +158,6 @@ void dvmUnlockHeap() dvmUnlockMutex(&gDvm.gcHeapLock); } -/* Pop an object from the list of pending finalizations and - * reference clears/enqueues, and return the object. - * The caller must call dvmReleaseTrackedAlloc() - * on the object when finished. - * - * Typically only called by the heap worker thread. - */ -Object *dvmGetNextHeapWorkerObject() -{ - Object *obj; - GcHeap *gcHeap = gDvm.gcHeap; - - dvmLockMutex(&gDvm.heapWorkerListLock); - - obj = dvmHeapGetNextObjectFromLargeTable(&gcHeap->referenceOperations); - if (obj != NULL) { - /* Don't let the GC collect the object until the - * worker thread is done with it. - */ - dvmAddTrackedAlloc(obj, NULL); - } - - dvmUnlockMutex(&gDvm.heapWorkerListLock); - - return obj; -} - /* Do a full garbage collection, which may grow the * heap as a side-effect if the live set is large. */ @@ -577,13 +531,6 @@ void dvmCollectGarbageInternal(const GcSpec* spec) gcHeap->gcRunning = true; - /* - * Grab the heapWorkerLock to prevent the HeapWorker thread from - * doing work. If it's executing a finalizer or an enqueue operation - * it won't be holding the lock, so this should return quickly. - */ - dvmLockMutex(&gDvm.heapWorkerLock); - rootSuspend = dvmGetRelativeTimeMsec(); dvmSuspendAllThreads(SUSPEND_FOR_GC); rootStart = dvmGetRelativeTimeMsec(); @@ -596,21 +543,6 @@ void dvmCollectGarbageInternal(const GcSpec* spec) if (!spec->isConcurrent) { oldThreadPriority = raiseThreadPriority(); } - - /* Make sure that the HeapWorker thread hasn't become - * wedged inside interp code. If it has, this call will - * print a message and abort the VM. - */ - dvmAssertHeapWorkerThreadRunning(); - - /* Lock the pendingFinalizationRefs list. - * - * Acquire the lock after suspending so the finalizer - * thread can't block in the RUNNING state while - * we try to suspend. - */ - dvmLockMutex(&gDvm.heapWorkerListLock); - if (gDvm.preVerify) { LOGV_HEAP("Verifying roots and heap before GC"); verifyRootsAndHeap(); @@ -634,9 +566,11 @@ void dvmCollectGarbageInternal(const GcSpec* spec) /* dvmHeapScanMarkedObjects() will build the lists of known * instances of the Reference classes. */ - gcHeap->softReferences = NULL; - gcHeap->weakReferences = NULL; - gcHeap->phantomReferences = NULL; + assert(gcHeap->softReferences == NULL); + assert(gcHeap->weakReferences == NULL); + assert(gcHeap->finalizerReferences == NULL); + assert(gcHeap->phantomReferences == NULL); + assert(gcHeap->clearedReferences == NULL); if (spec->isConcurrent) { /* @@ -747,25 +681,12 @@ void dvmCollectGarbageInternal(const GcSpec* spec) currAllocated = dvmHeapSourceGetValue(HS_BYTES_ALLOCATED, NULL, 0); currFootprint = dvmHeapSourceGetValue(HS_FOOTPRINT, NULL, 0); - /* Now that we've freed up the GC heap, return any large - * free chunks back to the system. They'll get paged back - * in the next time they're used. Don't do it immediately, - * though; if the process is still allocating a bunch of - * memory, we'll be taking a ton of page faults that we don't - * necessarily need to. - * - * Cancel any old scheduled trims, and schedule a new one. - */ - dvmScheduleHeapSourceTrim(5); // in seconds - dvmMethodTraceGCEnd(); LOGV_HEAP("GC finished"); gcHeap->gcRunning = false; LOGV_HEAP("Resuming threads"); - dvmUnlockMutex(&gDvm.heapWorkerListLock); - dvmUnlockMutex(&gDvm.heapWorkerLock); if (spec->isConcurrent) { /* @@ -787,6 +708,11 @@ void dvmCollectGarbageInternal(const GcSpec* spec) } } + /* + * Move queue of pending references back into Java. + */ + dvmEnqueueClearedReferences(&gDvm.gcHeap->clearedReferences); + percentFree = 100 - (size_t)(100.0f * (float)currAllocated / currFootprint); if (!spec->isConcurrent) { u4 markSweepTime = dirtyEnd - rootStart; diff --git a/vm/alloc/HeapInternal.h b/vm/alloc/HeapInternal.h index 7f8c9c5e3..1d1be0756 100644 --- a/vm/alloc/HeapInternal.h +++ b/vm/alloc/HeapInternal.h @@ -19,9 +19,6 @@ #ifndef _DALVIK_ALLOC_HEAP_INTERNAL #define _DALVIK_ALLOC_HEAP_INTERNAL -#include <time.h> // for struct timespec - -#include "HeapTable.h" #include "MarkSweep.h" typedef struct HeapSource HeapSource; @@ -39,34 +36,9 @@ struct GcHeap { Object *finalizerReferences; Object *phantomReferences; - /* The list of Reference objects that need to be cleared and/or - * enqueued. The bottom two bits of the object pointers indicate - * whether they should be cleared and/or enqueued. - * - * This table is protected by gDvm.heapWorkerListLock, which must - * be acquired after the heap lock. - */ - LargeHeapRefTable *referenceOperations; - - /* If non-null, the method that the HeapWorker is currently - * executing. - */ - Object *heapWorkerCurrentObject; - Method *heapWorkerCurrentMethod; - - /* If heapWorkerCurrentObject is non-null, this gives the time when - * HeapWorker started executing that method. The time value must come - * from dvmGetRelativeTimeUsec(). - * - * The "Cpu" entry tracks the per-thread CPU timer (when available). - */ - u8 heapWorkerInterpStartTime; - u8 heapWorkerInterpCpuStartTime; - - /* If any fields are non-zero, indicates the next (absolute) time that - * the HeapWorker thread should call dvmHeapSourceTrim(). + /* The list of Reference objects that need to be enqueued. */ - struct timespec heapWorkerNextTrim; + Object *clearedReferences; /* The current state of the mark step. * Only valid during a GC. diff --git a/vm/alloc/HeapTable.c b/vm/alloc/HeapTable.c deleted file mode 100644 index 6c9034a12..000000000 --- a/vm/alloc/HeapTable.c +++ /dev/null @@ -1,168 +0,0 @@ -/* - * 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/HeapTable.h" -#include "alloc/HeapInternal.h" - -#include <limits.h> // for INT_MAX - -static const int kLargeHeapRefTableNElems = 1024; -static const int kFinalizableRefDefault = 128; - -bool dvmHeapInitHeapRefTable(ReferenceTable *refs) -{ - return dvmInitReferenceTable(refs, kFinalizableRefDefault, INT_MAX); -} - -/* - * Large, non-contiguous reference tables - */ - -bool dvmHeapAddRefToLargeTable(LargeHeapRefTable **tableP, Object *ref) -{ - LargeHeapRefTable *table; - - assert(tableP != NULL); - assert(ref != NULL); - - /* Make sure that a table with a free slot is - * at the head of the list. - */ - if (*tableP != NULL) { - table = *tableP; - LargeHeapRefTable *prevTable; - - /* Find an empty slot for this reference. - */ - prevTable = NULL; - while (table != NULL && dvmIsReferenceTableFull(&table->refs)) { - prevTable = table; - table = table->next; - } - if (table != NULL) { - if (prevTable != NULL) { - /* Move the table to the head of the list. - */ - prevTable->next = table->next; - table->next = *tableP; - *tableP = table; - } - /* else it's already at the head. */ - - goto insert; - } - /* else all tables are already full; - * fall through to the alloc case. - */ - } - - /* Allocate a new table. - */ - table = (LargeHeapRefTable *)calloc(1, sizeof(LargeHeapRefTable)); - if (table == NULL) { - LOGE_HEAP("Can't allocate a new large ref table\n"); - return false; - } - if (!dvmInitReferenceTable(&table->refs, - kLargeHeapRefTableNElems, - INT_MAX)) { - LOGE_HEAP("Can't initialize a new large ref table\n"); - free(table); - return false; - } - - /* Stick it at the head. - */ - table->next = *tableP; - *tableP = table; - -insert: - /* Insert the reference. - */ - assert(table == *tableP); - assert(table != NULL); - assert(!dvmIsReferenceTableFull(&table->refs)); - *table->refs.nextEntry++ = ref; - - return true; -} - -bool dvmHeapAddTableToLargeTable(LargeHeapRefTable **tableP, ReferenceTable *refs) -{ - LargeHeapRefTable *table; - - /* Allocate a node. - */ - table = (LargeHeapRefTable *)calloc(1, sizeof(LargeHeapRefTable)); - if (table == NULL) { - LOGE_HEAP("Can't allocate a new large ref table\n"); - return false; - } - table->refs = *refs; - - /* Insert the table into the list. - */ - table->next = *tableP; - *tableP = table; - - return true; -} - -/* Frees everything associated with the LargeHeapRefTable. - */ -void dvmHeapFreeLargeTable(LargeHeapRefTable *table) -{ - while (table != NULL) { - LargeHeapRefTable *next = table->next; - dvmClearReferenceTable(&table->refs); - free(table); - table = next; - } -} - -Object *dvmHeapGetNextObjectFromLargeTable(LargeHeapRefTable **pTable) -{ - LargeHeapRefTable *table; - Object *obj; - - assert(pTable != NULL); - - obj = NULL; - table = *pTable; - if (table != NULL) { - ReferenceTable *refs = &table->refs; - - /* We should never have an empty table node in the list. - */ - assert(dvmReferenceTableEntries(refs) != 0); - - /* Remove and return the last entry in the list. - */ - obj = *--refs->nextEntry; - - /* If this was the last entry in the table node, - * free it and patch up the list. - */ - if (refs->nextEntry == refs->table) { - *pTable = table->next; - dvmClearReferenceTable(refs); - free(table); - } - } - - return obj; -} diff --git a/vm/alloc/HeapTable.h b/vm/alloc/HeapTable.h deleted file mode 100644 index 175111df1..000000000 --- a/vm/alloc/HeapTable.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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. - */ -#ifndef _DALVIK_ALLOC_HEAP_TABLE -#define _DALVIK_ALLOC_HEAP_TABLE - -#include "ReferenceTable.h" - -struct LargeHeapRefTable { - struct LargeHeapRefTable *next; - ReferenceTable refs; -}; - -typedef struct LargeHeapRefTable LargeHeapRefTable; - -bool dvmHeapInitHeapRefTable(ReferenceTable *refs); -void dvmHeapFreeLargeTable(LargeHeapRefTable *table); -bool dvmHeapAddRefToLargeTable(LargeHeapRefTable **tableP, Object *ref); -void dvmHeapMarkLargeTableRefs(LargeHeapRefTable *table); -bool dvmHeapAddTableToLargeTable(LargeHeapRefTable **tableP, - ReferenceTable *refs); -Object *dvmHeapGetNextObjectFromLargeTable(LargeHeapRefTable **pTable); - -#endif // _DALVIK_ALLOC_HEAP_TABLE diff --git a/vm/alloc/HeapWorker.c b/vm/alloc/HeapWorker.c deleted file mode 100644 index 60173818e..000000000 --- a/vm/alloc/HeapWorker.c +++ /dev/null @@ -1,472 +0,0 @@ -/* - * 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. - */ - -/* - * An async worker thread to handle certain heap operations that need - * to be done in a separate thread to avoid synchronization problems. - * HeapWorkers and reference enqueuing are handled by this thread. - * The VM does all clearing. - */ -#include "Dalvik.h" -#include "alloc/HeapInternal.h" -#include "alloc/HeapWorker.h" - -#include <sys/time.h> -#include <stdlib.h> -#include <pthread.h> -#include <signal.h> -#include <errno.h> // for ETIMEDOUT, etc. - -static void* heapWorkerThreadStart(void* arg); - -/* - * Initialize any HeapWorker state that Heap.c - * cares about. This lets the GC start before the - * HeapWorker thread is initialized. - */ -void dvmInitializeHeapWorkerState() -{ - assert(!gDvm.heapWorkerInitialized); - - dvmInitMutex(&gDvm.heapWorkerLock); - pthread_cond_init(&gDvm.heapWorkerCond, NULL); - pthread_cond_init(&gDvm.heapWorkerIdleCond, NULL); - - gDvm.heapWorkerInitialized = true; -} - -/* - * Crank up the heap worker thread. - * - * Does not return until the thread is ready for business. - */ -bool dvmHeapWorkerStartup(void) -{ - assert(!gDvm.haltHeapWorker); - assert(!gDvm.heapWorkerReady); - assert(gDvm.heapWorkerHandle == 0); - assert(gDvm.heapWorkerInitialized); - - /* use heapWorkerLock/heapWorkerCond to communicate readiness */ - dvmLockMutex(&gDvm.heapWorkerLock); - -//BUG: If a GC happens in here or in the new thread while we hold the lock, -// the GC will deadlock when trying to acquire heapWorkerLock. - if (!dvmCreateInternalThread(&gDvm.heapWorkerHandle, - "HeapWorker", heapWorkerThreadStart, NULL)) - { - dvmUnlockMutex(&gDvm.heapWorkerLock); - return false; - } - - /* - * Wait for the heap worker to come up. We know the thread was created, - * so this should not get stuck. - */ - while (!gDvm.heapWorkerReady) { - dvmWaitCond(&gDvm.heapWorkerCond, &gDvm.heapWorkerLock); - } - - dvmUnlockMutex(&gDvm.heapWorkerLock); - return true; -} - -/* - * Shut down the heap worker thread if it was started. - */ -void dvmHeapWorkerShutdown(void) -{ - void* threadReturn; - - /* note: assuming that (pthread_t)0 is not a valid thread handle */ - if (gDvm.heapWorkerHandle != 0) { - gDvm.haltHeapWorker = true; - dvmSignalHeapWorker(true); - - /* - * We may not want to wait for the heapWorkers to complete. It's - * a good idea to do so, in case they're holding some sort of OS - * resource that doesn't get reclaimed when the process exits - * (e.g. an open temp file). - */ - if (pthread_join(gDvm.heapWorkerHandle, &threadReturn) != 0) - LOGW("HeapWorker thread join failed\n"); - else if (gDvm.verboseShutdown) - LOGD("HeapWorker thread has shut down\n"); - - gDvm.heapWorkerReady = false; - } -} - -/* Make sure that the HeapWorker thread hasn't spent an inordinate - * amount of time inside a finalizer. - * - * Aborts the VM if the thread appears to be wedged. - * - * The caller must hold the heapWorkerLock to guarantee an atomic - * read of the watchdog values. - */ -void dvmAssertHeapWorkerThreadRunning() -{ - if (gDvm.gcHeap->heapWorkerCurrentObject != NULL) { - static const u8 HEAP_WORKER_WATCHDOG_TIMEOUT = 10*1000*1000LL; // 10sec - - u8 heapWorkerInterpStartTime = gDvm.gcHeap->heapWorkerInterpStartTime; - u8 now = dvmGetRelativeTimeUsec(); - u8 delta = now - heapWorkerInterpStartTime; - - if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT && - (gDvm.debuggerActive || gDvm.nativeDebuggerActive)) - { - /* - * Debugger suspension can block the thread indefinitely. For - * best results we should reset this explicitly whenever the - * HeapWorker thread is resumed. Unfortunately this is also - * affected by native debuggers, and we have no visibility - * into how they're manipulating us. So, we ignore the - * watchdog and just reset the timer. - */ - LOGI("Debugger is attached -- suppressing HeapWorker watchdog\n"); - gDvm.gcHeap->heapWorkerInterpStartTime = now; /* reset timer */ - } else if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT) { - /* - * Before we give up entirely, see if maybe we're just not - * getting any CPU time because we're stuck in a background - * process group. If we successfully move the thread into the - * foreground we'll just leave it there (it doesn't do anything - * if the process isn't GCing). - */ - dvmLockThreadList(NULL); - Thread* thread = dvmGetThreadByHandle(gDvm.heapWorkerHandle); - dvmUnlockThreadList(); - - if (thread != NULL) { - int priChangeFlags, threadPrio; - SchedPolicy threadPolicy; - priChangeFlags = dvmRaiseThreadPriorityIfNeeded(thread, - &threadPrio, &threadPolicy); - if (priChangeFlags != 0) { - LOGI("HeapWorker watchdog expired, raising priority" - " and retrying\n"); - gDvm.gcHeap->heapWorkerInterpStartTime = now; - return; - } - } - - char* desc = dexProtoCopyMethodDescriptor( - &gDvm.gcHeap->heapWorkerCurrentMethod->prototype); - LOGE("HeapWorker is wedged: %lldms spent inside %s.%s%s\n", - delta / 1000, - gDvm.gcHeap->heapWorkerCurrentObject->clazz->descriptor, - gDvm.gcHeap->heapWorkerCurrentMethod->name, desc); - free(desc); - dvmDumpAllThreads(true); - - /* try to get a debuggerd dump from the target thread */ - dvmNukeThread(thread); - - /* abort the VM */ - dvmAbort(); - } else if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT / 2) { - char* desc = dexProtoCopyMethodDescriptor( - &gDvm.gcHeap->heapWorkerCurrentMethod->prototype); - LOGW("HeapWorker may be wedged: %lldms spent inside %s.%s%s\n", - delta / 1000, - gDvm.gcHeap->heapWorkerCurrentObject->clazz->descriptor, - gDvm.gcHeap->heapWorkerCurrentMethod->name, desc); - free(desc); - } - } -} - -/* - * Acquires a mutex, transitioning to the VMWAIT state if the mutex is - * held. This allows the thread to suspend while it waits for another - * thread to release the mutex. - */ -static void lockMutex(pthread_mutex_t *mu) -{ - Thread *self; - ThreadStatus oldStatus; - - assert(mu != NULL); - if (dvmTryLockMutex(mu) != 0) { - self = dvmThreadSelf(); - assert(self != NULL); - oldStatus = dvmChangeStatus(self, THREAD_VMWAIT); - dvmLockMutex(mu); - dvmChangeStatus(self, oldStatus); - } -} - -static void callMethod(Thread *self, Object *obj, Method *method) -{ - JValue unused; - - /* Keep track of the method we're about to call and - * the current time so that other threads can detect - * when this thread wedges and provide useful information. - */ - gDvm.gcHeap->heapWorkerInterpStartTime = dvmGetRelativeTimeUsec(); - gDvm.gcHeap->heapWorkerInterpCpuStartTime = dvmGetThreadCpuTimeUsec(); - gDvm.gcHeap->heapWorkerCurrentMethod = method; - gDvm.gcHeap->heapWorkerCurrentObject = obj; - - /* Call the method. - * - * Don't hold the lock when executing interpreted - * code. It may suspend, and the GC needs to grab - * heapWorkerLock. - */ - dvmUnlockMutex(&gDvm.heapWorkerLock); - if (false) { - /* Log entry/exit; this will likely flood the log enough to - * cause "logcat" to drop entries. - */ - char tmpTag[16]; - sprintf(tmpTag, "HW%d", self->systemTid); - LOG(LOG_DEBUG, tmpTag, "Call %s\n", method->clazz->descriptor); - dvmCallMethod(self, method, obj, &unused); - LOG(LOG_DEBUG, tmpTag, " done\n"); - } else { - dvmCallMethod(self, method, obj, &unused); - } - /* - * Reacquire the heap worker lock in a suspend-friendly way. - */ - lockMutex(&gDvm.heapWorkerLock); - - gDvm.gcHeap->heapWorkerCurrentObject = NULL; - gDvm.gcHeap->heapWorkerCurrentMethod = NULL; - gDvm.gcHeap->heapWorkerInterpStartTime = 0LL; - - /* Exceptions thrown during these calls interrupt - * the method, but are otherwise ignored. - */ - if (dvmCheckException(self)) { -#if DVM_SHOW_EXCEPTION >= 1 - LOGI("Uncaught exception thrown by finalizer (will be discarded):\n"); - dvmLogExceptionStackTrace(); -#endif - dvmClearException(self); - } -} - -/* Process all enqueued heap work, including finalizers and reference - * enqueueing. Clearing has already been done by the VM. - * - * Caller must hold gDvm.heapWorkerLock. - */ -static void doHeapWork(Thread *self) -{ - Object *obj; - size_t numReferencesEnqueued; - - assert(gDvm.methJavaLangRefReference_enqueueInternal != NULL); - numReferencesEnqueued = 0; - while ((obj = dvmGetNextHeapWorkerObject()) != NULL) { - /* Make sure the object hasn't been collected since - * being scheduled. - */ - assert(dvmIsValidObject(obj)); - - /* Call the appropriate method(s). - */ - assert(dvmGetFieldObject( - obj, gDvm.offJavaLangRefReference_queue) != NULL); - assert(dvmGetFieldObject( - obj, gDvm.offJavaLangRefReference_queueNext) == NULL); - numReferencesEnqueued++; - callMethod(self, obj, gDvm.methJavaLangRefReference_enqueueInternal); - - /* Let the GC collect the object. - */ - dvmReleaseTrackedAlloc(obj, self); - } - LOGV("Enqueued %zd references", numReferencesEnqueued); -} - -/* - * The heap worker thread sits quietly until the GC tells it there's work - * to do. - */ -static void* heapWorkerThreadStart(void* arg) -{ - Thread *self = dvmThreadSelf(); - - UNUSED_PARAMETER(arg); - - LOGV("HeapWorker thread started (threadid=%d)\n", self->threadId); - - /* tell the main thread that we're ready */ - lockMutex(&gDvm.heapWorkerLock); - gDvm.heapWorkerReady = true; - dvmSignalCond(&gDvm.heapWorkerCond); - dvmUnlockMutex(&gDvm.heapWorkerLock); - - lockMutex(&gDvm.heapWorkerLock); - while (!gDvm.haltHeapWorker) { - struct timespec trimtime; - bool timedwait = false; - - /* We're done running interpreted code for now. */ - dvmChangeStatus(NULL, THREAD_VMWAIT); - - /* Signal anyone who wants to know when we're done. */ - dvmBroadcastCond(&gDvm.heapWorkerIdleCond); - - /* Trim the heap if we were asked to. */ - trimtime = gDvm.gcHeap->heapWorkerNextTrim; - if (trimtime.tv_sec != 0 && trimtime.tv_nsec != 0) { - struct timespec now; - -#ifdef HAVE_TIMEDWAIT_MONOTONIC - clock_gettime(CLOCK_MONOTONIC, &now); // relative time -#else - struct timeval tvnow; - gettimeofday(&tvnow, NULL); // absolute time - now.tv_sec = tvnow.tv_sec; - now.tv_nsec = tvnow.tv_usec * 1000; -#endif - - if (trimtime.tv_sec < now.tv_sec || - (trimtime.tv_sec == now.tv_sec && - trimtime.tv_nsec <= now.tv_nsec)) - { - size_t madvisedSizes[HEAP_SOURCE_MAX_HEAP_COUNT]; - - /* - * Acquire the gcHeapLock. The requires releasing the - * heapWorkerLock before the gcHeapLock is acquired. - * It is possible that the gcHeapLock may be acquired - * during a concurrent GC in which case heapWorkerLock - * is held by the GC and we are unable to make forward - * progress. We avoid deadlock by releasing the - * gcHeapLock and then waiting to be signaled when the - * GC completes. There is no guarantee that the next - * time we are run will coincide with GC inactivity so - * the check and wait must be performed within a loop. - */ - dvmUnlockMutex(&gDvm.heapWorkerLock); - dvmLockHeap(); - dvmWaitForConcurrentGcToComplete(); - dvmLockMutex(&gDvm.heapWorkerLock); - - memset(madvisedSizes, 0, sizeof(madvisedSizes)); - dvmHeapSourceTrim(madvisedSizes, HEAP_SOURCE_MAX_HEAP_COUNT); - - dvmUnlockHeap(); - - trimtime.tv_sec = 0; - trimtime.tv_nsec = 0; - gDvm.gcHeap->heapWorkerNextTrim = trimtime; - } else { - timedwait = true; - } - } - - /* sleep until signaled */ - if (timedwait) { - int cc __attribute__ ((__unused__)); -#ifdef HAVE_TIMEDWAIT_MONOTONIC - cc = pthread_cond_timedwait_monotonic(&gDvm.heapWorkerCond, - &gDvm.heapWorkerLock, &trimtime); -#else - cc = pthread_cond_timedwait(&gDvm.heapWorkerCond, - &gDvm.heapWorkerLock, &trimtime); -#endif - assert(cc == 0 || cc == ETIMEDOUT); - } else { - dvmWaitCond(&gDvm.heapWorkerCond, &gDvm.heapWorkerLock); - } - - /* - * Return to the running state before doing heap work. This - * will block if the GC has initiated a suspend. We release - * the heapWorkerLock beforehand for the GC to make progress - * and wait to be signaled after the GC completes. There is - * no guarantee that the next time we are run will coincide - * with GC inactivity so the check and wait must be performed - * within a loop. - */ - dvmUnlockMutex(&gDvm.heapWorkerLock); - dvmChangeStatus(NULL, THREAD_RUNNING); - dvmLockHeap(); - dvmWaitForConcurrentGcToComplete(); - dvmLockMutex(&gDvm.heapWorkerLock); - dvmUnlockHeap(); - LOGV("HeapWorker is awake\n"); - - /* Process any events in the queue. - */ - doHeapWork(self); - } - dvmUnlockMutex(&gDvm.heapWorkerLock); - - if (gDvm.verboseShutdown) - LOGD("HeapWorker thread shutting down\n"); - return NULL; -} - -/* - * Wake up the heap worker to let it know that there's work to be done. - */ -void dvmSignalHeapWorker(bool shouldLock) -{ - if (shouldLock) { - dvmLockMutex(&gDvm.heapWorkerLock); - } - - dvmSignalCond(&gDvm.heapWorkerCond); - - if (shouldLock) { - dvmUnlockMutex(&gDvm.heapWorkerLock); - } -} - -/* - * Requests that dvmHeapSourceTrim() be called no sooner - * than timeoutSec seconds from now. If timeoutSec - * is zero, any pending trim is cancelled. - * - * Caller must hold heapWorkerLock. - */ -void dvmScheduleHeapSourceTrim(size_t timeoutSec) -{ - GcHeap *gcHeap = gDvm.gcHeap; - struct timespec timeout; - - if (timeoutSec == 0) { - timeout.tv_sec = 0; - timeout.tv_nsec = 0; - /* Don't wake up the thread just to tell it to cancel. - * If it wakes up naturally, we can avoid the extra - * context switch. - */ - } else { -#ifdef HAVE_TIMEDWAIT_MONOTONIC - clock_gettime(CLOCK_MONOTONIC, &timeout); - timeout.tv_sec += timeoutSec; -#else - struct timeval now; - gettimeofday(&now, NULL); - timeout.tv_sec = now.tv_sec + timeoutSec; - timeout.tv_nsec = now.tv_usec * 1000; -#endif - dvmSignalHeapWorker(false); - } - gcHeap->heapWorkerNextTrim = timeout; -} diff --git a/vm/alloc/HeapWorker.h b/vm/alloc/HeapWorker.h deleted file mode 100644 index 67babc3b5..000000000 --- a/vm/alloc/HeapWorker.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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. - */ -/* - * Manage async heap tasks. - */ -#ifndef _DALVIK_ALLOC_HEAP_WORKER -#define _DALVIK_ALLOC_HEAP_WORKER - -/* - * Initialize any HeapWorker state that Heap.c - * cares about. This lets the GC start before the - * HeapWorker thread is initialized. - */ -void dvmInitializeHeapWorkerState(void); - -/* - * Initialization. Starts/stops the worker thread. - */ -bool dvmHeapWorkerStartup(void); -void dvmHeapWorkerShutdown(void); - -/* - * Tell the worker thread to wake up and do work. - * If shouldLock is false, the caller must have already - * acquired gDvm.heapWorkerLock. - */ -void dvmSignalHeapWorker(bool shouldLock); - -/* - * Requests that dvmHeapSourceTrim() be called no sooner - * than timeoutSec seconds from now. If timeoutSec - * is zero, any pending trim is cancelled. - * - * Caller must hold heapWorkerLock. - */ -void dvmScheduleHeapSourceTrim(size_t timeoutSec); - -/* Make sure that the HeapWorker thread hasn't spent an inordinate - * amount of time inside interpreted code. - * - * Aborts the VM if the thread appears to be wedged. - * - * The caller must hold the heapWorkerLock. - */ -void dvmAssertHeapWorkerThreadRunning(); - -/* - * Called by the worker thread to get the next object - * to finalize/enqueue/clear. Implemented in Heap.c. - * - * @param op The operation to perform on the returned object. - * Must be non-NULL. - * @return The object to operate on, or NULL. - */ -Object *dvmGetNextHeapWorkerObject(); - -#endif /*_DALVIK_ALLOC_HEAP_WORKER*/ diff --git a/vm/alloc/MarkSweep.c b/vm/alloc/MarkSweep.c index 69b75de3f..e26394c85 100644 --- a/vm/alloc/MarkSweep.c +++ b/vm/alloc/MarkSweep.c @@ -20,7 +20,6 @@ #include "alloc/HeapBitmapInlines.h" #include "alloc/HeapInternal.h" #include "alloc/HeapSource.h" -#include "alloc/HeapWorker.h" #include "alloc/MarkSweep.h" #include "alloc/Visit.h" #include "alloc/VisitInlines.h" @@ -342,8 +341,6 @@ static void verifyImmuneObjects() * - 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: @@ -795,11 +792,7 @@ static void enqueueReference(Object *ref) assert(ref != NULL); assert(dvmGetFieldObject(ref, gDvm.offJavaLangRefReference_queue) != NULL); assert(dvmGetFieldObject(ref, gDvm.offJavaLangRefReference_queueNext) == NULL); - if (!dvmHeapAddRefToLargeTable(&gDvm.gcHeap->referenceOperations, ref)) { - LOGE_HEAP("enqueueReference(): no room for any more " - "reference operations\n"); - dvmAbort(); - } + enqueuePendingReference(ref, &gDvm.gcHeap->clearedReferences); } /* @@ -857,11 +850,9 @@ static void clearWhiteReferences(Object **list) GcMarkContext *ctx; Object *ref, *referent; size_t referentOffset; - bool doSignal; ctx = &gDvm.gcHeap->markContext; referentOffset = gDvm.offJavaLangRefReference_referent; - doSignal = false; while (*list != NULL) { ref = dequeuePendingReference(list); referent = dvmGetFieldObject(ref, referentOffset); @@ -870,31 +861,23 @@ static void clearWhiteReferences(Object **list) clearReference(ref); if (isEnqueuable(ref)) { enqueueReference(ref); - doSignal = true; } } } - /* - * If we cleared a reference with a reference queue we must notify - * the heap worker to append the reference. - */ - if (doSignal) { - dvmSignalHeapWorker(false); - } assert(*list == NULL); } /* * Enqueues finalizer references with white referents. White - * referents are blackened, moved to the pendingNext field, and the + * referents are blackened, moved to the zombie field, and the * referent field is cleared. */ static void enqueueFinalizerReferences(Object **list) { GcMarkContext *ctx = &gDvm.gcHeap->markContext; size_t referentOffset = gDvm.offJavaLangRefReference_referent; - size_t pendingNextOffset = gDvm.offJavaLangRefReference_pendingNext; - bool doSignal = false; + size_t zombieOffset = gDvm.offJavaLangRefFinalizerReference_zombie; + bool hasEnqueued = false; while (*list != NULL) { Object *ref = dequeuePendingReference(list); Object *referent = dvmGetFieldObject(ref, referentOffset); @@ -902,15 +885,14 @@ static void enqueueFinalizerReferences(Object **list) markObject(referent, ctx); /* If the referent is non-null the reference must queuable. */ assert(isEnqueuable(ref)); - dvmSetFieldObject(ref, pendingNextOffset, referent); + dvmSetFieldObject(ref, zombieOffset, referent); clearReference(ref); enqueueReference(ref); - doSignal = true; + hasEnqueued = true; } } - if (doSignal) { + if (hasEnqueued) { processMarkStack(ctx); - dvmSignalHeapWorker(false); } assert(*list == NULL); } @@ -928,8 +910,8 @@ void dvmSetFinalizable(Object *obj) assert(self != NULL); Method *meth = gDvm.methJavaLangRefFinalizerReferenceAdd; assert(meth != NULL); - JValue unused; - dvmCallMethod(self, meth, obj, &unused, obj); + JValue unusedResult; + dvmCallMethod(self, meth, NULL, &unusedResult, obj); } /* @@ -982,6 +964,24 @@ void dvmHeapProcessReferences(Object **softReferences, bool clearSoftRefs, assert(*phantomReferences == NULL); } +/* + * Pushes a list of cleared references out to the managed heap. + */ +void dvmEnqueueClearedReferences(Object **cleared) +{ + assert(cleared != NULL); + if (*cleared != NULL) { + Thread *self = dvmThreadSelf(); + assert(self != NULL); + Method *meth = gDvm.methJavaLangRefReferenceQueueAdd; + assert(meth != NULL); + JValue unused; + Object *reference = *cleared; + dvmCallMethod(self, meth, NULL, &unused, reference); + *cleared = NULL; + } +} + void dvmHeapFinishMarkStep() { GcMarkContext *ctx; diff --git a/vm/alloc/MarkSweep.h b/vm/alloc/MarkSweep.h index c9f11e487..94bf3ad69 100644 --- a/vm/alloc/MarkSweep.h +++ b/vm/alloc/MarkSweep.h @@ -59,5 +59,6 @@ void dvmHeapFinishMarkStep(void); void dvmHeapSweepSystemWeaks(void); void dvmHeapSweepUnmarkedObjects(bool isPartial, bool isConcurrent, size_t *numObjects, size_t *numBytes); +void dvmEnqueueClearedReferences(Object **references); #endif // _DALVIK_ALLOC_MARK_SWEEP diff --git a/vm/alloc/Visit.c b/vm/alloc/Visit.c index 634611cde..6ca143992 100644 --- a/vm/alloc/Visit.c +++ b/vm/alloc/Visit.c @@ -83,20 +83,6 @@ static void visitIndirectRefTable(RootVisitor *visitor, IndirectRefTable *table, } /* - * Visits a large heap reference table. These objects are list heads. - * As such, it is valid for table to be NULL. - */ -static void visitLargeHeapRefTable(RootVisitor *visitor, - LargeHeapRefTable *table, - RootType type, void *arg) -{ - assert(visitor != NULL); - for (; table != NULL; table = table->next) { - visitReferenceTable(visitor, &table->refs, 0, type, arg); - } -} - -/* * Visits all stack slots except those belonging to native method * arguments. */ @@ -250,7 +236,6 @@ void dvmVisitRoots(RootVisitor *visitor, void *arg) dvmLockMutex(&gDvm.jniPinRefLock); visitReferenceTable(visitor, &gDvm.jniPinRefTable, 0, ROOT_VM_INTERNAL, arg); dvmUnlockMutex(&gDvm.jniPinRefLock); - visitLargeHeapRefTable(visitor, gDvm.gcHeap->referenceOperations, ROOT_REFERENCE_CLEANUP, arg); visitThreads(visitor, arg); (*visitor)(&gDvm.outOfMemoryObj, 0, ROOT_VM_INTERNAL, arg); (*visitor)(&gDvm.internalErrorObj, 0, ROOT_VM_INTERNAL, arg); diff --git a/vm/alloc/Visit.h b/vm/alloc/Visit.h index e7c52e58f..a66839d14 100644 --- a/vm/alloc/Visit.h +++ b/vm/alloc/Visit.h @@ -30,9 +30,7 @@ typedef enum { ROOT_MONITOR_USED, ROOT_THREAD_OBJECT, ROOT_INTERNED_STRING, - ROOT_FINALIZING, ROOT_DEBUGGER, - ROOT_REFERENCE_CLEANUP, ROOT_VM_INTERNAL, ROOT_JNI_MONITOR, } RootType; |