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 dvmWaitCond(&gDvm.heapWorkerCond, &gDvm.heapWorkerLock); 80 } 81 82 dvmUnlockMutex(&gDvm.heapWorkerLock); 83 return true; 84 } 85 86 /* 87 * Shut down the heap worker thread if it was started. 88 */ 89 void 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 if (gDvm.verboseShutdown) 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 */ 121 void 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 if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT && 131 (gDvm.debuggerActive || gDvm.nativeDebuggerActive)) 132 { 133 /* 134 * Debugger suspension can block the thread indefinitely. For 135 * best results we should reset this explicitly whenever the 136 * HeapWorker thread is resumed. Unfortunately this is also 137 * affected by native debuggers, and we have no visibility 138 * into how they're manipulating us. So, we ignore the 139 * watchdog and just reset the timer. 140 */ 141 LOGI("Debugger is attached -- suppressing HeapWorker watchdog\n"); 142 gDvm.gcHeap->heapWorkerInterpStartTime = now; /* reset timer */ 143 } else if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT) { 144 /* 145 * Before we give up entirely, see if maybe we're just not 146 * getting any CPU time because we're stuck in a background 147 * process group. If we successfully move the thread into the 148 * foreground we'll just leave it there (it doesn't do anything 149 * if the process isn't GCing). 150 */ 151 dvmLockThreadList(NULL); 152 Thread* thread = dvmGetThreadByHandle(gDvm.heapWorkerHandle); 153 dvmUnlockThreadList(); 154 155 if (thread != NULL) { 156 int priChangeFlags, threadPrio; 157 SchedPolicy threadPolicy; 158 priChangeFlags = dvmRaiseThreadPriorityIfNeeded(thread, 159 &threadPrio, &threadPolicy); 160 if (priChangeFlags != 0) { 161 LOGI("HeapWorker watchdog expired, raising priority" 162 " and retrying\n"); 163 gDvm.gcHeap->heapWorkerInterpStartTime = now; 164 return; 165 } 166 } 167 168 char* desc = dexProtoCopyMethodDescriptor( 169 &gDvm.gcHeap->heapWorkerCurrentMethod->prototype); 170 LOGE("HeapWorker is wedged: %lldms spent inside %s.%s%s\n", 171 delta / 1000, 172 gDvm.gcHeap->heapWorkerCurrentObject->clazz->descriptor, 173 gDvm.gcHeap->heapWorkerCurrentMethod->name, desc); 174 free(desc); 175 dvmDumpAllThreads(true); 176 177 /* try to get a debuggerd dump from the target thread */ 178 dvmNukeThread(thread); 179 180 /* abort the VM */ 181 dvmAbort(); 182 } else if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT / 2) { 183 char* desc = dexProtoCopyMethodDescriptor( 184 &gDvm.gcHeap->heapWorkerCurrentMethod->prototype); 185 LOGW("HeapWorker may be wedged: %lldms spent inside %s.%s%s\n", 186 delta / 1000, 187 gDvm.gcHeap->heapWorkerCurrentObject->clazz->descriptor, 188 gDvm.gcHeap->heapWorkerCurrentMethod->name, desc); 189 free(desc); 190 } 191 } 192 } 193 194 /* 195 * Acquires a mutex, transitioning to the VMWAIT state if the mutex is 196 * held. This allows the thread to suspend while it waits for another 197 * thread to release the mutex. 198 */ 199 static void lockMutex(pthread_mutex_t *mu) 200 { 201 Thread *self; 202 ThreadStatus oldStatus; 203 204 assert(mu != NULL); 205 if (dvmTryLockMutex(mu) != 0) { 206 self = dvmThreadSelf(); 207 assert(self != NULL); 208 oldStatus = dvmChangeStatus(self, THREAD_VMWAIT); 209 dvmLockMutex(mu); 210 dvmChangeStatus(self, oldStatus); 211 } 212 } 213 214 static void callMethod(Thread *self, Object *obj, Method *method) 215 { 216 JValue unused; 217 218 /* Keep track of the method we're about to call and 219 * the current time so that other threads can detect 220 * when this thread wedges and provide useful information. 221 */ 222 gDvm.gcHeap->heapWorkerInterpStartTime = dvmGetRelativeTimeUsec(); 223 gDvm.gcHeap->heapWorkerInterpCpuStartTime = dvmGetThreadCpuTimeUsec(); 224 gDvm.gcHeap->heapWorkerCurrentMethod = method; 225 gDvm.gcHeap->heapWorkerCurrentObject = obj; 226 227 /* Call the method. 228 * 229 * Don't hold the lock when executing interpreted 230 * code. It may suspend, and the GC needs to grab 231 * heapWorkerLock. 232 */ 233 dvmUnlockMutex(&gDvm.heapWorkerLock); 234 if (false) { 235 /* Log entry/exit; this will likely flood the log enough to 236 * cause "logcat" to drop entries. 237 */ 238 char tmpTag[16]; 239 sprintf(tmpTag, "HW%d", self->systemTid); 240 LOG(LOG_DEBUG, tmpTag, "Call %s\n", method->clazz->descriptor); 241 dvmCallMethod(self, method, obj, &unused); 242 LOG(LOG_DEBUG, tmpTag, " done\n"); 243 } else { 244 dvmCallMethod(self, method, obj, &unused); 245 } 246 /* 247 * Reacquire the heap worker lock in a suspend-friendly way. 248 */ 249 lockMutex(&gDvm.heapWorkerLock); 250 251 gDvm.gcHeap->heapWorkerCurrentObject = NULL; 252 gDvm.gcHeap->heapWorkerCurrentMethod = NULL; 253 gDvm.gcHeap->heapWorkerInterpStartTime = 0LL; 254 255 /* Exceptions thrown during these calls interrupt 256 * the method, but are otherwise ignored. 257 */ 258 if (dvmCheckException(self)) { 259 #if DVM_SHOW_EXCEPTION >= 1 260 LOGI("Uncaught exception thrown by finalizer (will be discarded):\n"); 261 dvmLogExceptionStackTrace(); 262 #endif 263 dvmClearException(self); 264 } 265 } 266 267 /* Process all enqueued heap work, including finalizers and reference 268 * enqueueing. Clearing has already been done by the VM. 269 * 270 * Caller must hold gDvm.heapWorkerLock. 271 */ 272 static void doHeapWork(Thread *self) 273 { 274 Object *obj; 275 HeapWorkerOperation op; 276 int numFinalizersCalled, numReferencesEnqueued; 277 278 assert(gDvm.voffJavaLangObject_finalize >= 0); 279 assert(gDvm.methJavaLangRefReference_enqueueInternal != NULL); 280 281 numFinalizersCalled = 0; 282 numReferencesEnqueued = 0; 283 while ((obj = dvmGetNextHeapWorkerObject(&op)) != NULL) { 284 Method *method = NULL; 285 286 /* Make sure the object hasn't been collected since 287 * being scheduled. 288 */ 289 assert(dvmIsValidObject(obj)); 290 291 /* Call the appropriate method(s). 292 */ 293 if (op == WORKER_FINALIZE) { 294 numFinalizersCalled++; 295 method = obj->clazz->vtable[gDvm.voffJavaLangObject_finalize]; 296 assert(dvmCompareNameDescriptorAndMethod("finalize", "()V", 297 method) == 0); 298 assert(method->clazz != gDvm.classJavaLangObject); 299 callMethod(self, obj, method); 300 } else { 301 assert(op == WORKER_ENQUEUE); 302 assert(dvmGetFieldObject( 303 obj, gDvm.offJavaLangRefReference_queue) != NULL); 304 assert(dvmGetFieldObject( 305 obj, gDvm.offJavaLangRefReference_queueNext) == NULL); 306 numReferencesEnqueued++; 307 callMethod(self, obj, 308 gDvm.methJavaLangRefReference_enqueueInternal); 309 } 310 311 /* Let the GC collect the object. 312 */ 313 dvmReleaseTrackedAlloc(obj, self); 314 } 315 LOGV("Called %d finalizers\n", numFinalizersCalled); 316 LOGV("Enqueued %d references\n", numReferencesEnqueued); 317 } 318 319 /* 320 * The heap worker thread sits quietly until the GC tells it there's work 321 * to do. 322 */ 323 static void* heapWorkerThreadStart(void* arg) 324 { 325 Thread *self = dvmThreadSelf(); 326 327 UNUSED_PARAMETER(arg); 328 329 LOGV("HeapWorker thread started (threadid=%d)\n", self->threadId); 330 331 /* tell the main thread that we're ready */ 332 lockMutex(&gDvm.heapWorkerLock); 333 gDvm.heapWorkerReady = true; 334 dvmSignalCond(&gDvm.heapWorkerCond); 335 dvmUnlockMutex(&gDvm.heapWorkerLock); 336 337 lockMutex(&gDvm.heapWorkerLock); 338 while (!gDvm.haltHeapWorker) { 339 struct timespec trimtime; 340 bool timedwait = false; 341 342 /* We're done running interpreted code for now. */ 343 dvmChangeStatus(NULL, THREAD_VMWAIT); 344 345 /* Signal anyone who wants to know when we're done. */ 346 dvmBroadcastCond(&gDvm.heapWorkerIdleCond); 347 348 /* Trim the heap if we were asked to. */ 349 trimtime = gDvm.gcHeap->heapWorkerNextTrim; 350 if (trimtime.tv_sec != 0 && trimtime.tv_nsec != 0) { 351 struct timespec now; 352 353 #ifdef HAVE_TIMEDWAIT_MONOTONIC 354 clock_gettime(CLOCK_MONOTONIC, &now); // relative time 355 #else 356 struct timeval tvnow; 357 gettimeofday(&tvnow, NULL); // absolute time 358 now.tv_sec = tvnow.tv_sec; 359 now.tv_nsec = tvnow.tv_usec * 1000; 360 #endif 361 362 if (trimtime.tv_sec < now.tv_sec || 363 (trimtime.tv_sec == now.tv_sec && 364 trimtime.tv_nsec <= now.tv_nsec)) 365 { 366 size_t madvisedSizes[HEAP_SOURCE_MAX_HEAP_COUNT]; 367 368 /* 369 * Acquire the gcHeapLock. The requires releasing the 370 * heapWorkerLock before the gcHeapLock is acquired. 371 * It is possible that the gcHeapLock may be acquired 372 * during a concurrent GC in which case heapWorkerLock 373 * is held by the GC and we are unable to make forward 374 * progress. We avoid deadlock by releasing the 375 * gcHeapLock and then waiting to be signaled when the 376 * GC completes. There is no guarantee that the next 377 * time we are run will coincide with GC inactivity so 378 * the check and wait must be performed within a loop. 379 */ 380 dvmUnlockMutex(&gDvm.heapWorkerLock); 381 dvmLockHeap(); 382 while (gDvm.gcHeap->gcRunning) { 383 dvmWaitForConcurrentGcToComplete(); 384 } 385 dvmLockMutex(&gDvm.heapWorkerLock); 386 387 memset(madvisedSizes, 0, sizeof(madvisedSizes)); 388 dvmHeapSourceTrim(madvisedSizes, HEAP_SOURCE_MAX_HEAP_COUNT); 389 dvmLogMadviseStats(madvisedSizes, HEAP_SOURCE_MAX_HEAP_COUNT); 390 391 dvmUnlockHeap(); 392 393 trimtime.tv_sec = 0; 394 trimtime.tv_nsec = 0; 395 gDvm.gcHeap->heapWorkerNextTrim = trimtime; 396 } else { 397 timedwait = true; 398 } 399 } 400 401 /* sleep until signaled */ 402 if (timedwait) { 403 int cc __attribute__ ((__unused__)); 404 #ifdef HAVE_TIMEDWAIT_MONOTONIC 405 cc = pthread_cond_timedwait_monotonic(&gDvm.heapWorkerCond, 406 &gDvm.heapWorkerLock, &trimtime); 407 #else 408 cc = pthread_cond_timedwait(&gDvm.heapWorkerCond, 409 &gDvm.heapWorkerLock, &trimtime); 410 #endif 411 assert(cc == 0 || cc == ETIMEDOUT); 412 } else { 413 dvmWaitCond(&gDvm.heapWorkerCond, &gDvm.heapWorkerLock); 414 } 415 416 /* 417 * Return to the running state before doing heap work. This 418 * will block if the GC has initiated a suspend. We release 419 * the heapWorkerLock beforehand for the GC to make progress 420 * and wait to be signaled after the GC completes. There is 421 * no guarantee that the next time we are run will coincide 422 * with GC inactivity so the check and wait must be performed 423 * within a loop. 424 */ 425 dvmUnlockMutex(&gDvm.heapWorkerLock); 426 dvmChangeStatus(NULL, THREAD_RUNNING); 427 dvmLockHeap(); 428 while (gDvm.gcHeap->gcRunning) { 429 dvmWaitForConcurrentGcToComplete(); 430 } 431 dvmLockMutex(&gDvm.heapWorkerLock); 432 dvmUnlockHeap(); 433 LOGV("HeapWorker is awake\n"); 434 435 /* Process any events in the queue. 436 */ 437 doHeapWork(self); 438 } 439 dvmUnlockMutex(&gDvm.heapWorkerLock); 440 441 if (gDvm.verboseShutdown) 442 LOGD("HeapWorker thread shutting down\n"); 443 return NULL; 444 } 445 446 /* 447 * Wake up the heap worker to let it know that there's work to be done. 448 */ 449 void dvmSignalHeapWorker(bool shouldLock) 450 { 451 if (shouldLock) { 452 dvmLockMutex(&gDvm.heapWorkerLock); 453 } 454 455 dvmSignalCond(&gDvm.heapWorkerCond); 456 457 if (shouldLock) { 458 dvmUnlockMutex(&gDvm.heapWorkerLock); 459 } 460 } 461 462 /* 463 * Block until all pending heap worker work has finished. 464 */ 465 void dvmWaitForHeapWorkerIdle() 466 { 467 assert(gDvm.heapWorkerReady); 468 469 dvmChangeStatus(NULL, THREAD_VMWAIT); 470 471 dvmLockMutex(&gDvm.heapWorkerLock); 472 473 /* Wake up the heap worker and wait for it to finish. */ 474 //TODO(http://b/issue?id=699704): This will deadlock if 475 // called from finalize(), enqueue(), or clear(). We 476 // need to detect when this is called from the HeapWorker 477 // context and just give up. 478 dvmSignalHeapWorker(false); 479 dvmWaitCond(&gDvm.heapWorkerIdleCond, &gDvm.heapWorkerLock); 480 481 dvmUnlockMutex(&gDvm.heapWorkerLock); 482 483 dvmChangeStatus(NULL, THREAD_RUNNING); 484 } 485 486 /* 487 * Do not return until any pending heap work has finished. This may 488 * or may not happen in the context of the calling thread. 489 * No exceptions will escape. 490 */ 491 void dvmRunFinalizationSync() 492 { 493 if (gDvm.zygote) { 494 assert(!gDvm.heapWorkerReady); 495 496 /* When in zygote mode, there is no heap worker. 497 * Do the work in the current thread. 498 */ 499 dvmLockMutex(&gDvm.heapWorkerLock); 500 doHeapWork(dvmThreadSelf()); 501 dvmUnlockMutex(&gDvm.heapWorkerLock); 502 } else { 503 /* Outside of zygote mode, we can just ask the 504 * heap worker thread to do the work. 505 */ 506 dvmWaitForHeapWorkerIdle(); 507 } 508 } 509 510 /* 511 * Requests that dvmHeapSourceTrim() be called no sooner 512 * than timeoutSec seconds from now. If timeoutSec 513 * is zero, any pending trim is cancelled. 514 * 515 * Caller must hold heapWorkerLock. 516 */ 517 void dvmScheduleHeapSourceTrim(size_t timeoutSec) 518 { 519 GcHeap *gcHeap = gDvm.gcHeap; 520 struct timespec timeout; 521 522 if (timeoutSec == 0) { 523 timeout.tv_sec = 0; 524 timeout.tv_nsec = 0; 525 /* Don't wake up the thread just to tell it to cancel. 526 * If it wakes up naturally, we can avoid the extra 527 * context switch. 528 */ 529 } else { 530 #ifdef HAVE_TIMEDWAIT_MONOTONIC 531 clock_gettime(CLOCK_MONOTONIC, &timeout); 532 timeout.tv_sec += timeoutSec; 533 #else 534 struct timeval now; 535 gettimeofday(&now, NULL); 536 timeout.tv_sec = now.tv_sec + timeoutSec; 537 timeout.tv_nsec = now.tv_usec * 1000; 538 #endif 539 dvmSignalHeapWorker(false); 540 } 541 gcHeap->heapWorkerNextTrim = timeout; 542 } 543