/* * 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 "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) { 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; HeapWorkerOperation op; int numFinalizersCalled, numReferencesEnqueued; assert(gDvm.voffJavaLangObject_finalize >= 0); assert(gDvm.methJavaLangRefReference_enqueueInternal != NULL); 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 { assert(op == WORKER_ENQUEUE); 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("Called %d finalizers\n", numFinalizersCalled); LOGV("Enqueued %d references\n", 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(); while (gDvm.gcHeap->gcRunning) { dvmWaitForConcurrentGcToComplete(); } 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) { 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(); while (gDvm.gcHeap->gcRunning) { 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); } } /* * Block until all pending heap worker work has finished. */ void dvmWaitForHeapWorkerIdle() { 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); dvmWaitCond(&gDvm.heapWorkerIdleCond, &gDvm.heapWorkerLock); 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 { #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; }