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