/* * 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 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 || 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"); 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 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]; /* 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) { #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 || 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; #ifdef HAVE_TIMEDWAIT_MONOTONIC clock_gettime(CLOCK_MONOTONIC, &timeout); timeout.tv_sec += timeoutSec; #else gettimeofday(&now, NULL); timeout.tv_sec = now.tv_sec + timeoutSec; timeout.tv_nsec = now.tv_usec * 1000; #endif dvmSignalHeapWorker(false); } gcHeap->heapWorkerNextTrim = timeout; }