Home | History | Annotate | Download | only in alloc
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 /*
     18  * An async worker thread to handle certain heap operations that need
     19  * to be done in a separate thread to avoid synchronization problems.
     20  * HeapWorkers and reference enqueuing are handled by this thread.
     21  * The VM does all clearing.
     22  */
     23 #include "Dalvik.h"
     24 #include "HeapInternal.h"
     25 
     26 #include <sys/time.h>
     27 #include <stdlib.h>
     28 #include <pthread.h>
     29 #include <signal.h>
     30 #include <errno.h>  // for ETIMEDOUT, etc.
     31 
     32 static void* heapWorkerThreadStart(void* arg);
     33 
     34 /*
     35  * Initialize any HeapWorker state that Heap.c
     36  * cares about.  This lets the GC start before the
     37  * HeapWorker thread is initialized.
     38  */
     39 void dvmInitializeHeapWorkerState()
     40 {
     41     assert(!gDvm.heapWorkerInitialized);
     42 
     43     dvmInitMutex(&gDvm.heapWorkerLock);
     44     pthread_cond_init(&gDvm.heapWorkerCond, NULL);
     45     pthread_cond_init(&gDvm.heapWorkerIdleCond, NULL);
     46 
     47     gDvm.heapWorkerInitialized = true;
     48 }
     49 
     50 /*
     51  * Crank up the heap worker thread.
     52  *
     53  * Does not return until the thread is ready for business.
     54  */
     55 bool dvmHeapWorkerStartup(void)
     56 {
     57     assert(!gDvm.haltHeapWorker);
     58     assert(!gDvm.heapWorkerReady);
     59     assert(gDvm.heapWorkerHandle == 0);
     60     assert(gDvm.heapWorkerInitialized);
     61 
     62     /* use heapWorkerLock/heapWorkerCond to communicate readiness */
     63     dvmLockMutex(&gDvm.heapWorkerLock);
     64 
     65 //BUG: If a GC happens in here or in the new thread while we hold the lock,
     66 //     the GC will deadlock when trying to acquire heapWorkerLock.
     67     if (!dvmCreateInternalThread(&gDvm.heapWorkerHandle,
     68                 "HeapWorker", heapWorkerThreadStart, NULL))
     69     {
     70         dvmUnlockMutex(&gDvm.heapWorkerLock);
     71         return false;
     72     }
     73 
     74     /*
     75      * Wait for the heap worker to come up.  We know the thread was created,
     76      * so this should not get stuck.
     77      */
     78     while (!gDvm.heapWorkerReady) {
     79         int cc = pthread_cond_wait(&gDvm.heapWorkerCond, &gDvm.heapWorkerLock);
     80         assert(cc == 0);
     81     }
     82 
     83     dvmUnlockMutex(&gDvm.heapWorkerLock);
     84     return true;
     85 }
     86 
     87 /*
     88  * Shut down the heap worker thread if it was started.
     89  */
     90 void dvmHeapWorkerShutdown(void)
     91 {
     92     void* threadReturn;
     93 
     94     /* note: assuming that (pthread_t)0 is not a valid thread handle */
     95     if (gDvm.heapWorkerHandle != 0) {
     96         gDvm.haltHeapWorker = true;
     97         dvmSignalHeapWorker(true);
     98 
     99         /*
    100          * We may not want to wait for the heapWorkers to complete.  It's
    101          * a good idea to do so, in case they're holding some sort of OS
    102          * resource that doesn't get reclaimed when the process exits
    103          * (e.g. an open temp file).
    104          */
    105         if (pthread_join(gDvm.heapWorkerHandle, &threadReturn) != 0)
    106             LOGW("HeapWorker thread join failed\n");
    107         else if (gDvm.verboseShutdown)
    108             LOGD("HeapWorker thread has shut down\n");
    109 
    110         gDvm.heapWorkerReady = false;
    111     }
    112 }
    113 
    114 /* Make sure that the HeapWorker thread hasn't spent an inordinate
    115  * amount of time inside a finalizer.
    116  *
    117  * Aborts the VM if the thread appears to be wedged.
    118  *
    119  * The caller must hold the heapWorkerLock to guarantee an atomic
    120  * read of the watchdog values.
    121  */
    122 void dvmAssertHeapWorkerThreadRunning()
    123 {
    124     if (gDvm.gcHeap->heapWorkerCurrentObject != NULL) {
    125         static const u8 HEAP_WORKER_WATCHDOG_TIMEOUT = 10*1000*1000LL; // 10sec
    126 
    127         u8 heapWorkerInterpStartTime = gDvm.gcHeap->heapWorkerInterpStartTime;
    128         u8 now = dvmGetRelativeTimeUsec();
    129         u8 delta = now - heapWorkerInterpStartTime;
    130 
    131         u8 heapWorkerInterpCpuStartTime =
    132             gDvm.gcHeap->heapWorkerInterpCpuStartTime;
    133         u8 nowCpu = dvmGetOtherThreadCpuTimeUsec(gDvm.heapWorkerHandle);
    134         u8 deltaCpu = nowCpu - heapWorkerInterpCpuStartTime;
    135 
    136         if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT &&
    137             (gDvm.debuggerActive || gDvm.nativeDebuggerActive))
    138         {
    139             /*
    140              * Debugger suspension can block the thread indefinitely.  For
    141              * best results we should reset this explicitly whenever the
    142              * HeapWorker thread is resumed.  Unfortunately this is also
    143              * affected by native debuggers, and we have no visibility
    144              * into how they're manipulating us.  So, we ignore the
    145              * watchdog and just reset the timer.
    146              */
    147             LOGI("Debugger is attached -- suppressing HeapWorker watchdog\n");
    148             gDvm.gcHeap->heapWorkerInterpStartTime = now;   /* reset timer */
    149         } else if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT) {
    150             /*
    151              * Before we give up entirely, see if maybe we're just not
    152              * getting any CPU time because we're stuck in a background
    153              * process group.  If we successfully move the thread into the
    154              * foreground we'll just leave it there (it doesn't do anything
    155              * if the process isn't GCing).
    156              */
    157             dvmLockThreadList(NULL);
    158             Thread* thread = dvmGetThreadByHandle(gDvm.heapWorkerHandle);
    159             dvmUnlockThreadList();
    160 
    161             if (thread != NULL) {
    162                 int priChangeFlags, threadPrio;
    163                 SchedPolicy threadPolicy;
    164                 priChangeFlags = dvmRaiseThreadPriorityIfNeeded(thread,
    165                         &threadPrio, &threadPolicy);
    166                 if (priChangeFlags != 0) {
    167                     LOGI("HeapWorker watchdog expired, raising priority"
    168                          " and retrying\n");
    169                     gDvm.gcHeap->heapWorkerInterpStartTime = now;
    170                     return;
    171                 }
    172             }
    173 
    174             char* desc = dexProtoCopyMethodDescriptor(
    175                     &gDvm.gcHeap->heapWorkerCurrentMethod->prototype);
    176             LOGE("HeapWorker is wedged: %lldms spent inside %s.%s%s\n",
    177                     delta / 1000,
    178                     gDvm.gcHeap->heapWorkerCurrentObject->clazz->descriptor,
    179                     gDvm.gcHeap->heapWorkerCurrentMethod->name, desc);
    180             free(desc);
    181             dvmDumpAllThreads(true);
    182 
    183             /* try to get a debuggerd dump from the target thread */
    184             dvmNukeThread(thread);
    185 
    186             /* abort the VM */
    187             dvmAbort();
    188         } else if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT / 2) {
    189             char* desc = dexProtoCopyMethodDescriptor(
    190                     &gDvm.gcHeap->heapWorkerCurrentMethod->prototype);
    191             LOGW("HeapWorker may be wedged: %lldms spent inside %s.%s%s\n",
    192                     delta / 1000,
    193                     gDvm.gcHeap->heapWorkerCurrentObject->clazz->descriptor,
    194                     gDvm.gcHeap->heapWorkerCurrentMethod->name, desc);
    195             free(desc);
    196         }
    197     }
    198 }
    199 
    200 static void callMethod(Thread *self, Object *obj, Method *method)
    201 {
    202     JValue unused;
    203 
    204     /* Keep track of the method we're about to call and
    205      * the current time so that other threads can detect
    206      * when this thread wedges and provide useful information.
    207      */
    208     gDvm.gcHeap->heapWorkerInterpStartTime = dvmGetRelativeTimeUsec();
    209     gDvm.gcHeap->heapWorkerInterpCpuStartTime = dvmGetThreadCpuTimeUsec();
    210     gDvm.gcHeap->heapWorkerCurrentMethod = method;
    211     gDvm.gcHeap->heapWorkerCurrentObject = obj;
    212 
    213     /* Call the method.
    214      *
    215      * Don't hold the lock when executing interpreted
    216      * code.  It may suspend, and the GC needs to grab
    217      * heapWorkerLock.
    218      */
    219     dvmUnlockMutex(&gDvm.heapWorkerLock);
    220     if (false) {
    221         /* Log entry/exit; this will likely flood the log enough to
    222          * cause "logcat" to drop entries.
    223          */
    224         char tmpTag[16];
    225         sprintf(tmpTag, "HW%d", self->systemTid);
    226         LOG(LOG_DEBUG, tmpTag, "Call %s\n", method->clazz->descriptor);
    227         dvmCallMethod(self, method, obj, &unused);
    228         LOG(LOG_DEBUG, tmpTag, " done\n");
    229     } else {
    230         dvmCallMethod(self, method, obj, &unused);
    231     }
    232     dvmLockMutex(&gDvm.heapWorkerLock);
    233 
    234     gDvm.gcHeap->heapWorkerCurrentObject = NULL;
    235     gDvm.gcHeap->heapWorkerCurrentMethod = NULL;
    236     gDvm.gcHeap->heapWorkerInterpStartTime = 0LL;
    237 
    238     /* Exceptions thrown during these calls interrupt
    239      * the method, but are otherwise ignored.
    240      */
    241     if (dvmCheckException(self)) {
    242 #if DVM_SHOW_EXCEPTION >= 1
    243         LOGI("Uncaught exception thrown by finalizer (will be discarded):\n");
    244         dvmLogExceptionStackTrace();
    245 #endif
    246         dvmClearException(self);
    247     }
    248 }
    249 
    250 /* Process all enqueued heap work, including finalizers and reference
    251  * enqueueing. Clearing has already been done by the VM.
    252  *
    253  * Caller must hold gDvm.heapWorkerLock.
    254  */
    255 static void doHeapWork(Thread *self)
    256 {
    257     Object *obj;
    258     HeapWorkerOperation op;
    259     int numFinalizersCalled, numReferencesEnqueued;
    260 
    261     assert(gDvm.voffJavaLangObject_finalize >= 0);
    262     assert(gDvm.methJavaLangRefReference_enqueueInternal != NULL);
    263 
    264     numFinalizersCalled = 0;
    265     numReferencesEnqueued = 0;
    266     while ((obj = dvmGetNextHeapWorkerObject(&op)) != NULL) {
    267         Method *method = NULL;
    268 
    269         /* Make sure the object hasn't been collected since
    270          * being scheduled.
    271          */
    272         assert(dvmIsValidObject(obj));
    273 
    274         /* Call the appropriate method(s).
    275          */
    276         if (op == WORKER_FINALIZE) {
    277             numFinalizersCalled++;
    278             method = obj->clazz->vtable[gDvm.voffJavaLangObject_finalize];
    279             assert(dvmCompareNameDescriptorAndMethod("finalize", "()V",
    280                             method) == 0);
    281             assert(method->clazz != gDvm.classJavaLangObject);
    282             callMethod(self, obj, method);
    283         } else {
    284             if (op & WORKER_ENQUEUE) {
    285                 numReferencesEnqueued++;
    286                 callMethod(self, obj,
    287                         gDvm.methJavaLangRefReference_enqueueInternal);
    288             }
    289         }
    290 
    291         /* Let the GC collect the object.
    292          */
    293         dvmReleaseTrackedAlloc(obj, self);
    294     }
    295     LOGV("Called %d finalizers\n", numFinalizersCalled);
    296     LOGV("Enqueued %d references\n", numReferencesEnqueued);
    297 }
    298 
    299 /*
    300  * The heap worker thread sits quietly until the GC tells it there's work
    301  * to do.
    302  */
    303 static void* heapWorkerThreadStart(void* arg)
    304 {
    305     Thread *self = dvmThreadSelf();
    306     int cc;
    307 
    308     UNUSED_PARAMETER(arg);
    309 
    310     LOGV("HeapWorker thread started (threadid=%d)\n", self->threadId);
    311 
    312     /* tell the main thread that we're ready */
    313     dvmLockMutex(&gDvm.heapWorkerLock);
    314     gDvm.heapWorkerReady = true;
    315     cc = pthread_cond_signal(&gDvm.heapWorkerCond);
    316     assert(cc == 0);
    317     dvmUnlockMutex(&gDvm.heapWorkerLock);
    318 
    319     dvmLockMutex(&gDvm.heapWorkerLock);
    320     while (!gDvm.haltHeapWorker) {
    321         struct timespec trimtime;
    322         bool timedwait = false;
    323 
    324         /* We're done running interpreted code for now. */
    325         dvmChangeStatus(NULL, THREAD_VMWAIT);
    326 
    327         /* Signal anyone who wants to know when we're done. */
    328         cc = pthread_cond_broadcast(&gDvm.heapWorkerIdleCond);
    329         assert(cc == 0);
    330 
    331         /* Trim the heap if we were asked to. */
    332         trimtime = gDvm.gcHeap->heapWorkerNextTrim;
    333         if (trimtime.tv_sec != 0 && trimtime.tv_nsec != 0) {
    334             struct timespec now;
    335 
    336 #ifdef HAVE_TIMEDWAIT_MONOTONIC
    337             clock_gettime(CLOCK_MONOTONIC, &now);       // relative time
    338 #else
    339             struct timeval tvnow;
    340             gettimeofday(&tvnow, NULL);                 // absolute time
    341             now.tv_sec = tvnow.tv_sec;
    342             now.tv_nsec = tvnow.tv_usec * 1000;
    343 #endif
    344 
    345             if (trimtime.tv_sec < now.tv_sec ||
    346                 (trimtime.tv_sec == now.tv_sec &&
    347                  trimtime.tv_nsec <= now.tv_nsec))
    348             {
    349                 size_t madvisedSizes[HEAP_SOURCE_MAX_HEAP_COUNT];
    350 
    351                 /* The heap must be locked before the HeapWorker;
    352                  * unroll and re-order the locks.  dvmLockHeap()
    353                  * will put us in VMWAIT if necessary.  Once it
    354                  * returns, there shouldn't be any contention on
    355                  * heapWorkerLock.
    356                  */
    357                 dvmUnlockMutex(&gDvm.heapWorkerLock);
    358                 dvmLockHeap();
    359                 dvmLockMutex(&gDvm.heapWorkerLock);
    360 
    361                 memset(madvisedSizes, 0, sizeof(madvisedSizes));
    362                 dvmHeapSourceTrim(madvisedSizes, HEAP_SOURCE_MAX_HEAP_COUNT);
    363                 dvmLogMadviseStats(madvisedSizes, HEAP_SOURCE_MAX_HEAP_COUNT);
    364 
    365                 dvmUnlockHeap();
    366 
    367                 trimtime.tv_sec = 0;
    368                 trimtime.tv_nsec = 0;
    369                 gDvm.gcHeap->heapWorkerNextTrim = trimtime;
    370             } else {
    371                 timedwait = true;
    372             }
    373         }
    374 
    375         /* sleep until signaled */
    376         if (timedwait) {
    377 #ifdef HAVE_TIMEDWAIT_MONOTONIC
    378             cc = pthread_cond_timedwait_monotonic(&gDvm.heapWorkerCond,
    379                     &gDvm.heapWorkerLock, &trimtime);
    380 #else
    381             cc = pthread_cond_timedwait(&gDvm.heapWorkerCond,
    382                     &gDvm.heapWorkerLock, &trimtime);
    383 #endif
    384             assert(cc == 0 || cc == ETIMEDOUT || cc == EINTR);
    385         } else {
    386             cc = pthread_cond_wait(&gDvm.heapWorkerCond, &gDvm.heapWorkerLock);
    387             assert(cc == 0);
    388         }
    389 
    390         /* dvmChangeStatus() may block;  don't hold heapWorkerLock.
    391          */
    392         dvmUnlockMutex(&gDvm.heapWorkerLock);
    393         dvmChangeStatus(NULL, THREAD_RUNNING);
    394         dvmLockMutex(&gDvm.heapWorkerLock);
    395         LOGV("HeapWorker is awake\n");
    396 
    397         /* Process any events in the queue.
    398          */
    399         doHeapWork(self);
    400     }
    401     dvmUnlockMutex(&gDvm.heapWorkerLock);
    402 
    403     if (gDvm.verboseShutdown)
    404         LOGD("HeapWorker thread shutting down\n");
    405     return NULL;
    406 }
    407 
    408 /*
    409  * Wake up the heap worker to let it know that there's work to be done.
    410  */
    411 void dvmSignalHeapWorker(bool shouldLock)
    412 {
    413     int cc;
    414 
    415     if (shouldLock) {
    416         dvmLockMutex(&gDvm.heapWorkerLock);
    417     }
    418 
    419     cc = pthread_cond_signal(&gDvm.heapWorkerCond);
    420     assert(cc == 0);
    421 
    422     if (shouldLock) {
    423         dvmUnlockMutex(&gDvm.heapWorkerLock);
    424     }
    425 }
    426 
    427 /*
    428  * Block until all pending heap worker work has finished.
    429  */
    430 void dvmWaitForHeapWorkerIdle()
    431 {
    432     int cc;
    433 
    434     assert(gDvm.heapWorkerReady);
    435 
    436     dvmChangeStatus(NULL, THREAD_VMWAIT);
    437 
    438     dvmLockMutex(&gDvm.heapWorkerLock);
    439 
    440     /* Wake up the heap worker and wait for it to finish. */
    441     //TODO(http://b/issue?id=699704): This will deadlock if
    442     //     called from finalize(), enqueue(), or clear().  We
    443     //     need to detect when this is called from the HeapWorker
    444     //     context and just give up.
    445     dvmSignalHeapWorker(false);
    446     cc = pthread_cond_wait(&gDvm.heapWorkerIdleCond, &gDvm.heapWorkerLock);
    447     assert(cc == 0);
    448 
    449     dvmUnlockMutex(&gDvm.heapWorkerLock);
    450 
    451     dvmChangeStatus(NULL, THREAD_RUNNING);
    452 }
    453 
    454 /*
    455  * Do not return until any pending heap work has finished.  This may
    456  * or may not happen in the context of the calling thread.
    457  * No exceptions will escape.
    458  */
    459 void dvmRunFinalizationSync()
    460 {
    461     if (gDvm.zygote) {
    462         assert(!gDvm.heapWorkerReady);
    463 
    464         /* When in zygote mode, there is no heap worker.
    465          * Do the work in the current thread.
    466          */
    467         dvmLockMutex(&gDvm.heapWorkerLock);
    468         doHeapWork(dvmThreadSelf());
    469         dvmUnlockMutex(&gDvm.heapWorkerLock);
    470     } else {
    471         /* Outside of zygote mode, we can just ask the
    472          * heap worker thread to do the work.
    473          */
    474         dvmWaitForHeapWorkerIdle();
    475     }
    476 }
    477 
    478 /*
    479  * Requests that dvmHeapSourceTrim() be called no sooner
    480  * than timeoutSec seconds from now.  If timeoutSec
    481  * is zero, any pending trim is cancelled.
    482  *
    483  * Caller must hold heapWorkerLock.
    484  */
    485 void dvmScheduleHeapSourceTrim(size_t timeoutSec)
    486 {
    487     GcHeap *gcHeap = gDvm.gcHeap;
    488     struct timespec timeout;
    489 
    490     if (timeoutSec == 0) {
    491         timeout.tv_sec = 0;
    492         timeout.tv_nsec = 0;
    493         /* Don't wake up the thread just to tell it to cancel.
    494          * If it wakes up naturally, we can avoid the extra
    495          * context switch.
    496          */
    497     } else {
    498         struct timeval now;
    499 
    500 #ifdef HAVE_TIMEDWAIT_MONOTONIC
    501         clock_gettime(CLOCK_MONOTONIC, &timeout);
    502         timeout.tv_sec += timeoutSec;
    503 #else
    504         gettimeofday(&now, NULL);
    505         timeout.tv_sec = now.tv_sec + timeoutSec;
    506         timeout.tv_nsec = now.tv_usec * 1000;
    507 #endif
    508         dvmSignalHeapWorker(false);
    509     }
    510     gcHeap->heapWorkerNextTrim = timeout;
    511 }
    512