diff options
| author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:28:47 -0800 |
|---|---|---|
| committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:28:47 -0800 |
| commit | f6c387128427e121477c1b32ad35cdcaa5101ba3 (patch) | |
| tree | 2aa25fa8c8c3a9caeecf98fd8ac4cd9b12717997 /vm/alloc/HeapWorker.c | |
| parent | f72d5de56a522ac3be03873bdde26f23a5eeeb3c (diff) | |
| download | android_dalvik-f6c387128427e121477c1b32ad35cdcaa5101ba3.tar.gz android_dalvik-f6c387128427e121477c1b32ad35cdcaa5101ba3.tar.bz2 android_dalvik-f6c387128427e121477c1b32ad35cdcaa5101ba3.zip | |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'vm/alloc/HeapWorker.c')
| -rw-r--r-- | vm/alloc/HeapWorker.c | 499 |
1 files changed, 499 insertions, 0 deletions
diff --git a/vm/alloc/HeapWorker.c b/vm/alloc/HeapWorker.c new file mode 100644 index 000000000..0244cca9f --- /dev/null +++ b/vm/alloc/HeapWorker.c @@ -0,0 +1,499 @@ +/* + * 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 clearing/enqueuing are + * handled by this thread. + */ +#include "Dalvik.h" +#include "HeapInternal.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) { + int cc = pthread_cond_wait(&gDvm.heapWorkerCond, &gDvm.heapWorkerLock); + assert(cc == 0); + } + + 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 + 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 interpreted 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; + + u8 heapWorkerInterpCpuStartTime = + gDvm.gcHeap->heapWorkerInterpCpuStartTime; + u8 nowCpu = dvmGetOtherThreadCpuTimeUsec(gDvm.heapWorkerHandle); + u8 deltaCpu = nowCpu - heapWorkerInterpCpuStartTime; + + if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT && gDvm.debuggerActive) { + /* + * Debugger suspension can block the thread indefinitely. For + * best results we should reset this explicitly whenever the + * HeapWorker thread is resumed. Ignoring the yelp isn't + * quite right but will do for a quick fix. + */ + LOGI("Debugger is attached -- suppressing HeapWorker watchdog\n"); + heapWorkerInterpStartTime = now; /* reset timer */ + } else if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT) { + 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); + + /* 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); + } + } +} + +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); + } + dvmLockMutex(&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 + * clearing/enqueueing. + * + * Caller must hold gDvm.heapWorkerLock. + */ +static void doHeapWork(Thread *self) +{ + Object *obj; + HeapWorkerOperation op; + int numFinalizersCalled, numReferencesEnqueued; +#if FANCY_REFERENCE_SUBCLASS + int numReferencesCleared = 0; +#endif + + assert(gDvm.voffJavaLangObject_finalize >= 0); +#if FANCY_REFERENCE_SUBCLASS + assert(gDvm.voffJavaLangRefReference_clear >= 0); + assert(gDvm.voffJavaLangRefReference_enqueue >= 0); +#else + assert(gDvm.methJavaLangRefReference_enqueueInternal != NULL); +#endif + + numFinalizersCalled = 0; + numReferencesEnqueued = 0; + while ((obj = dvmGetNextHeapWorkerObject(&op)) != NULL) { + Method *method = NULL; + + /* Make sure the object hasn't been collected since + * being scheduled. + */ + assert(dvmIsValidObject(obj)); + + /* Call the appropriate method(s). + */ + if (op == WORKER_FINALIZE) { + numFinalizersCalled++; + method = obj->clazz->vtable[gDvm.voffJavaLangObject_finalize]; + assert(dvmCompareNameDescriptorAndMethod("finalize", "()V", + method) == 0); + assert(method->clazz != gDvm.classJavaLangObject); + callMethod(self, obj, method); + } else { +#if FANCY_REFERENCE_SUBCLASS + /* clear() *must* happen before enqueue(), otherwise + * a non-clear reference could appear on a reference + * queue. + */ + if (op & WORKER_CLEAR) { + numReferencesCleared++; + method = obj->clazz->vtable[ + gDvm.voffJavaLangRefReference_clear]; + assert(dvmCompareNameDescriptorAndMethod("clear", "()V", + method) == 0); + assert(method->clazz != gDvm.classJavaLangRefReference); + callMethod(self, obj, method); + } + if (op & WORKER_ENQUEUE) { + numReferencesEnqueued++; + method = obj->clazz->vtable[ + gDvm.voffJavaLangRefReference_enqueue]; + assert(dvmCompareNameDescriptorAndMethod("enqueue", "()Z", + method) == 0); + /* We call enqueue() even when it isn't overridden, + * so don't assert(!classJavaLangRefReference) here. + */ + callMethod(self, obj, method); + } +#else + assert((op & WORKER_CLEAR) == 0); + if (op & WORKER_ENQUEUE) { + numReferencesEnqueued++; + callMethod(self, obj, + gDvm.methJavaLangRefReference_enqueueInternal); + } +#endif + } + + /* Let the GC collect the object. + */ + dvmReleaseTrackedAlloc(obj, self); + } + LOGV("Called %d finalizers\n", numFinalizersCalled); + LOGV("Enqueued %d references\n", numReferencesEnqueued); +#if FANCY_REFERENCE_SUBCLASS + LOGV("Cleared %d overridden references\n", numReferencesCleared); +#endif +} + +/* + * The heap worker thread sits quietly until the GC tells it there's work + * to do. + */ +static void* heapWorkerThreadStart(void* arg) +{ + Thread *self = dvmThreadSelf(); + int cc; + + UNUSED_PARAMETER(arg); + + LOGV("HeapWorker thread started (threadid=%d)\n", self->threadId); + + /* tell the main thread that we're ready */ + dvmLockMutex(&gDvm.heapWorkerLock); + gDvm.heapWorkerReady = true; + cc = pthread_cond_signal(&gDvm.heapWorkerCond); + assert(cc == 0); + dvmUnlockMutex(&gDvm.heapWorkerLock); + + dvmLockMutex(&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. */ + cc = pthread_cond_broadcast(&gDvm.heapWorkerIdleCond); + assert(cc == 0); + + /* Trim the heap if we were asked to. */ + trimtime = gDvm.gcHeap->heapWorkerNextTrim; + if (trimtime.tv_sec != 0 && trimtime.tv_nsec != 0) { + struct timeval now; + + gettimeofday(&now, NULL); + if (trimtime.tv_sec < now.tv_sec || + (trimtime.tv_sec == now.tv_sec && + trimtime.tv_nsec <= now.tv_usec * 1000)) + { + size_t madvisedSizes[HEAP_SOURCE_MAX_HEAP_COUNT]; + + /* The heap must be locked before the HeapWorker; + * unroll and re-order the locks. dvmLockHeap() + * will put us in VMWAIT if necessary. Once it + * returns, there shouldn't be any contention on + * heapWorkerLock. + */ + dvmUnlockMutex(&gDvm.heapWorkerLock); + dvmLockHeap(); + dvmLockMutex(&gDvm.heapWorkerLock); + + memset(madvisedSizes, 0, sizeof(madvisedSizes)); + dvmHeapSourceTrim(madvisedSizes, HEAP_SOURCE_MAX_HEAP_COUNT); + dvmLogMadviseStats(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) { + cc = pthread_cond_timedwait(&gDvm.heapWorkerCond, + &gDvm.heapWorkerLock, &trimtime); + assert(cc == 0 || cc == ETIMEDOUT || cc == EINTR); + } else { + cc = pthread_cond_wait(&gDvm.heapWorkerCond, &gDvm.heapWorkerLock); + assert(cc == 0); + } + + /* dvmChangeStatus() may block; don't hold heapWorkerLock. + */ + dvmUnlockMutex(&gDvm.heapWorkerLock); + dvmChangeStatus(NULL, THREAD_RUNNING); + dvmLockMutex(&gDvm.heapWorkerLock); + LOGV("HeapWorker is awake\n"); + + /* Process any events in the queue. + */ + doHeapWork(self); + } + dvmUnlockMutex(&gDvm.heapWorkerLock); + + 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) +{ + int cc; + + if (shouldLock) { + dvmLockMutex(&gDvm.heapWorkerLock); + } + + cc = pthread_cond_signal(&gDvm.heapWorkerCond); + assert(cc == 0); + + if (shouldLock) { + dvmUnlockMutex(&gDvm.heapWorkerLock); + } +} + +/* + * Block until all pending heap worker work has finished. + */ +void dvmWaitForHeapWorkerIdle() +{ + int cc; + + assert(gDvm.heapWorkerReady); + + dvmChangeStatus(NULL, THREAD_VMWAIT); + + dvmLockMutex(&gDvm.heapWorkerLock); + + /* Wake up the heap worker and wait for it to finish. */ + //TODO(http://b/issue?id=699704): This will deadlock if + // called from finalize(), enqueue(), or clear(). We + // need to detect when this is called from the HeapWorker + // context and just give up. + dvmSignalHeapWorker(false); + cc = pthread_cond_wait(&gDvm.heapWorkerIdleCond, &gDvm.heapWorkerLock); + assert(cc == 0); + + dvmUnlockMutex(&gDvm.heapWorkerLock); + + dvmChangeStatus(NULL, THREAD_RUNNING); +} + +/* + * Do not return until any pending heap work has finished. This may + * or may not happen in the context of the calling thread. + * No exceptions will escape. + */ +void dvmRunFinalizationSync() +{ + if (gDvm.zygote) { + assert(!gDvm.heapWorkerReady); + + /* When in zygote mode, there is no heap worker. + * Do the work in the current thread. + */ + dvmLockMutex(&gDvm.heapWorkerLock); + doHeapWork(dvmThreadSelf()); + dvmUnlockMutex(&gDvm.heapWorkerLock); + } else { + /* Outside of zygote mode, we can just ask the + * heap worker thread to do the work. + */ + dvmWaitForHeapWorkerIdle(); + } +} + +/* + * 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 { + struct timeval now; + + gettimeofday(&now, NULL); + timeout.tv_sec = now.tv_sec + timeoutSec; + timeout.tv_nsec = now.tv_usec * 1000; + dvmSignalHeapWorker(false); + } + gcHeap->heapWorkerNextTrim = timeout; +} |
