summaryrefslogtreecommitdiffstats
path: root/vm/alloc/HeapWorker.c
diff options
context:
space:
mode:
authorCarl Shapiro <cshapiro@google.com>2011-03-30 19:35:34 -0700
committerCarl Shapiro <cshapiro@google.com>2011-03-30 19:53:34 -0700
commitce87bfed41bbe4248b2770fb1a90f34b2518f6fa (patch)
tree8a2ca573187d322f7202df68d36a1101294f5ee5 /vm/alloc/HeapWorker.c
parent6920d2c5b83f8ff0cdc8f33770cd658969dbd262 (diff)
downloadandroid_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/HeapWorker.c')
-rw-r--r--vm/alloc/HeapWorker.c472
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;
-}