diff options
Diffstat (limited to 'vm/alloc/HeapWorker.c')
-rw-r--r-- | vm/alloc/HeapWorker.c | 472 |
1 files changed, 0 insertions, 472 deletions
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; -} |