blob: e7a4d03756c77fd2f23857776ac153a157caa50f [file] [log] [blame]
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001/*
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 */
Andy McFaddeneaf2ee02009-06-02 13:42:44 -070016
The Android Open Source Projectf6c38712009-03-03 19:28:47 -080017/*
18 * An async worker thread to handle certain heap operations that
19 * need to be done in a separate thread to avoid synchronization
20 * problems. HeapWorkers and reference clearing/enqueuing are
21 * handled by this thread.
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
32static 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 */
39void 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 */
55bool 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 */
90void 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
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
The Android Open Source Project99409882009-03-18 22:20:24 -0700115 * amount of time inside a finalizer.
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800116 *
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 */
122void 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
The Android Open Source Project99409882009-03-18 22:20:24 -0700136 if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT &&
137 (gDvm.debuggerActive || gDvm.nativeDebuggerActive))
138 {
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800139 /*
140 * Debugger suspension can block the thread indefinitely. For
141 * best results we should reset this explicitly whenever the
The Android Open Source Project99409882009-03-18 22:20:24 -0700142 * 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.
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800146 */
147 LOGI("Debugger is attached -- suppressing HeapWorker watchdog\n");
148 heapWorkerInterpStartTime = now; /* reset timer */
149 } else if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT) {
150 char* desc = dexProtoCopyMethodDescriptor(
151 &gDvm.gcHeap->heapWorkerCurrentMethod->prototype);
152 LOGE("HeapWorker is wedged: %lldms spent inside %s.%s%s\n",
153 delta / 1000,
154 gDvm.gcHeap->heapWorkerCurrentObject->clazz->descriptor,
155 gDvm.gcHeap->heapWorkerCurrentMethod->name, desc);
156 free(desc);
157 dvmDumpAllThreads(true);
158
159 /* abort the VM */
160 dvmAbort();
161 } else if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT / 2) {
162 char* desc = dexProtoCopyMethodDescriptor(
163 &gDvm.gcHeap->heapWorkerCurrentMethod->prototype);
164 LOGW("HeapWorker may be wedged: %lldms spent inside %s.%s%s\n",
165 delta / 1000,
166 gDvm.gcHeap->heapWorkerCurrentObject->clazz->descriptor,
167 gDvm.gcHeap->heapWorkerCurrentMethod->name, desc);
168 free(desc);
169 }
170 }
171}
172
173static void callMethod(Thread *self, Object *obj, Method *method)
174{
175 JValue unused;
176
177 /* Keep track of the method we're about to call and
178 * the current time so that other threads can detect
179 * when this thread wedges and provide useful information.
180 */
181 gDvm.gcHeap->heapWorkerInterpStartTime = dvmGetRelativeTimeUsec();
182 gDvm.gcHeap->heapWorkerInterpCpuStartTime = dvmGetThreadCpuTimeUsec();
183 gDvm.gcHeap->heapWorkerCurrentMethod = method;
184 gDvm.gcHeap->heapWorkerCurrentObject = obj;
185
186 /* Call the method.
187 *
188 * Don't hold the lock when executing interpreted
189 * code. It may suspend, and the GC needs to grab
190 * heapWorkerLock.
191 */
192 dvmUnlockMutex(&gDvm.heapWorkerLock);
193 if (false) {
194 /* Log entry/exit; this will likely flood the log enough to
195 * cause "logcat" to drop entries.
196 */
197 char tmpTag[16];
198 sprintf(tmpTag, "HW%d", self->systemTid);
199 LOG(LOG_DEBUG, tmpTag, "Call %s\n", method->clazz->descriptor);
200 dvmCallMethod(self, method, obj, &unused);
201 LOG(LOG_DEBUG, tmpTag, " done\n");
202 } else {
203 dvmCallMethod(self, method, obj, &unused);
204 }
205 dvmLockMutex(&gDvm.heapWorkerLock);
206
207 gDvm.gcHeap->heapWorkerCurrentObject = NULL;
208 gDvm.gcHeap->heapWorkerCurrentMethod = NULL;
209 gDvm.gcHeap->heapWorkerInterpStartTime = 0LL;
210
211 /* Exceptions thrown during these calls interrupt
212 * the method, but are otherwise ignored.
213 */
214 if (dvmCheckException(self)) {
215#if DVM_SHOW_EXCEPTION >= 1
216 LOGI("Uncaught exception thrown by finalizer (will be discarded):\n");
217 dvmLogExceptionStackTrace();
218#endif
219 dvmClearException(self);
220 }
221}
222
223/* Process all enqueued heap work, including finalizers and reference
224 * clearing/enqueueing.
225 *
226 * Caller must hold gDvm.heapWorkerLock.
227 */
228static void doHeapWork(Thread *self)
229{
230 Object *obj;
231 HeapWorkerOperation op;
232 int numFinalizersCalled, numReferencesEnqueued;
233#if FANCY_REFERENCE_SUBCLASS
234 int numReferencesCleared = 0;
235#endif
236
237 assert(gDvm.voffJavaLangObject_finalize >= 0);
238#if FANCY_REFERENCE_SUBCLASS
239 assert(gDvm.voffJavaLangRefReference_clear >= 0);
240 assert(gDvm.voffJavaLangRefReference_enqueue >= 0);
241#else
242 assert(gDvm.methJavaLangRefReference_enqueueInternal != NULL);
243#endif
244
245 numFinalizersCalled = 0;
246 numReferencesEnqueued = 0;
247 while ((obj = dvmGetNextHeapWorkerObject(&op)) != NULL) {
248 Method *method = NULL;
249
250 /* Make sure the object hasn't been collected since
251 * being scheduled.
252 */
253 assert(dvmIsValidObject(obj));
254
255 /* Call the appropriate method(s).
256 */
257 if (op == WORKER_FINALIZE) {
258 numFinalizersCalled++;
259 method = obj->clazz->vtable[gDvm.voffJavaLangObject_finalize];
260 assert(dvmCompareNameDescriptorAndMethod("finalize", "()V",
261 method) == 0);
262 assert(method->clazz != gDvm.classJavaLangObject);
263 callMethod(self, obj, method);
264 } else {
265#if FANCY_REFERENCE_SUBCLASS
266 /* clear() *must* happen before enqueue(), otherwise
267 * a non-clear reference could appear on a reference
268 * queue.
269 */
270 if (op & WORKER_CLEAR) {
271 numReferencesCleared++;
272 method = obj->clazz->vtable[
273 gDvm.voffJavaLangRefReference_clear];
274 assert(dvmCompareNameDescriptorAndMethod("clear", "()V",
275 method) == 0);
276 assert(method->clazz != gDvm.classJavaLangRefReference);
277 callMethod(self, obj, method);
278 }
279 if (op & WORKER_ENQUEUE) {
280 numReferencesEnqueued++;
281 method = obj->clazz->vtable[
282 gDvm.voffJavaLangRefReference_enqueue];
283 assert(dvmCompareNameDescriptorAndMethod("enqueue", "()Z",
284 method) == 0);
285 /* We call enqueue() even when it isn't overridden,
286 * so don't assert(!classJavaLangRefReference) here.
287 */
288 callMethod(self, obj, method);
289 }
290#else
291 assert((op & WORKER_CLEAR) == 0);
292 if (op & WORKER_ENQUEUE) {
293 numReferencesEnqueued++;
294 callMethod(self, obj,
295 gDvm.methJavaLangRefReference_enqueueInternal);
296 }
297#endif
298 }
299
300 /* Let the GC collect the object.
301 */
302 dvmReleaseTrackedAlloc(obj, self);
303 }
304 LOGV("Called %d finalizers\n", numFinalizersCalled);
305 LOGV("Enqueued %d references\n", numReferencesEnqueued);
306#if FANCY_REFERENCE_SUBCLASS
307 LOGV("Cleared %d overridden references\n", numReferencesCleared);
308#endif
309}
310
311/*
312 * The heap worker thread sits quietly until the GC tells it there's work
313 * to do.
314 */
315static void* heapWorkerThreadStart(void* arg)
316{
317 Thread *self = dvmThreadSelf();
318 int cc;
319
320 UNUSED_PARAMETER(arg);
321
322 LOGV("HeapWorker thread started (threadid=%d)\n", self->threadId);
323
324 /* tell the main thread that we're ready */
325 dvmLockMutex(&gDvm.heapWorkerLock);
326 gDvm.heapWorkerReady = true;
327 cc = pthread_cond_signal(&gDvm.heapWorkerCond);
328 assert(cc == 0);
329 dvmUnlockMutex(&gDvm.heapWorkerLock);
330
331 dvmLockMutex(&gDvm.heapWorkerLock);
332 while (!gDvm.haltHeapWorker) {
333 struct timespec trimtime;
334 bool timedwait = false;
335
336 /* We're done running interpreted code for now. */
337 dvmChangeStatus(NULL, THREAD_VMWAIT);
338
339 /* Signal anyone who wants to know when we're done. */
340 cc = pthread_cond_broadcast(&gDvm.heapWorkerIdleCond);
341 assert(cc == 0);
342
343 /* Trim the heap if we were asked to. */
344 trimtime = gDvm.gcHeap->heapWorkerNextTrim;
345 if (trimtime.tv_sec != 0 && trimtime.tv_nsec != 0) {
Andy McFaddeneaf2ee02009-06-02 13:42:44 -0700346 struct timespec now;
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800347
Andy McFaddeneaf2ee02009-06-02 13:42:44 -0700348#ifdef HAVE_TIMEDWAIT_MONOTONIC
349 clock_gettime(CLOCK_MONOTONIC, &now); // relative time
350#else
351 struct timeval tvnow;
352 gettimeofday(&tvnow, NULL); // absolute time
353 now.tv_sec = tvnow.tv_sec;
354 now.tv_nsec = tvnow.tv_usec * 1000;
355#endif
356
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800357 if (trimtime.tv_sec < now.tv_sec ||
358 (trimtime.tv_sec == now.tv_sec &&
Andy McFaddeneaf2ee02009-06-02 13:42:44 -0700359 trimtime.tv_nsec <= now.tv_nsec))
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800360 {
361 size_t madvisedSizes[HEAP_SOURCE_MAX_HEAP_COUNT];
362
363 /* The heap must be locked before the HeapWorker;
364 * unroll and re-order the locks. dvmLockHeap()
365 * will put us in VMWAIT if necessary. Once it
366 * returns, there shouldn't be any contention on
367 * heapWorkerLock.
368 */
369 dvmUnlockMutex(&gDvm.heapWorkerLock);
370 dvmLockHeap();
371 dvmLockMutex(&gDvm.heapWorkerLock);
372
373 memset(madvisedSizes, 0, sizeof(madvisedSizes));
374 dvmHeapSourceTrim(madvisedSizes, HEAP_SOURCE_MAX_HEAP_COUNT);
375 dvmLogMadviseStats(madvisedSizes, HEAP_SOURCE_MAX_HEAP_COUNT);
376
377 dvmUnlockHeap();
378
379 trimtime.tv_sec = 0;
380 trimtime.tv_nsec = 0;
381 gDvm.gcHeap->heapWorkerNextTrim = trimtime;
382 } else {
383 timedwait = true;
384 }
385 }
386
387 /* sleep until signaled */
388 if (timedwait) {
Andy McFaddeneaf2ee02009-06-02 13:42:44 -0700389#ifdef HAVE_TIMEDWAIT_MONOTONIC
390 cc = pthread_cond_timedwait_monotonic(&gDvm.heapWorkerCond,
391 &gDvm.heapWorkerLock, &trimtime);
392#else
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800393 cc = pthread_cond_timedwait(&gDvm.heapWorkerCond,
394 &gDvm.heapWorkerLock, &trimtime);
Andy McFaddeneaf2ee02009-06-02 13:42:44 -0700395#endif
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800396 assert(cc == 0 || cc == ETIMEDOUT || cc == EINTR);
397 } else {
398 cc = pthread_cond_wait(&gDvm.heapWorkerCond, &gDvm.heapWorkerLock);
399 assert(cc == 0);
400 }
401
402 /* dvmChangeStatus() may block; don't hold heapWorkerLock.
403 */
404 dvmUnlockMutex(&gDvm.heapWorkerLock);
405 dvmChangeStatus(NULL, THREAD_RUNNING);
406 dvmLockMutex(&gDvm.heapWorkerLock);
407 LOGV("HeapWorker is awake\n");
408
409 /* Process any events in the queue.
410 */
411 doHeapWork(self);
412 }
413 dvmUnlockMutex(&gDvm.heapWorkerLock);
414
415 LOGD("HeapWorker thread shutting down\n");
416 return NULL;
417}
418
419/*
420 * Wake up the heap worker to let it know that there's work to be done.
421 */
422void dvmSignalHeapWorker(bool shouldLock)
423{
424 int cc;
425
426 if (shouldLock) {
427 dvmLockMutex(&gDvm.heapWorkerLock);
428 }
429
430 cc = pthread_cond_signal(&gDvm.heapWorkerCond);
431 assert(cc == 0);
432
433 if (shouldLock) {
434 dvmUnlockMutex(&gDvm.heapWorkerLock);
435 }
436}
437
438/*
439 * Block until all pending heap worker work has finished.
440 */
441void dvmWaitForHeapWorkerIdle()
442{
443 int cc;
444
445 assert(gDvm.heapWorkerReady);
446
447 dvmChangeStatus(NULL, THREAD_VMWAIT);
448
449 dvmLockMutex(&gDvm.heapWorkerLock);
450
451 /* Wake up the heap worker and wait for it to finish. */
452 //TODO(http://b/issue?id=699704): This will deadlock if
453 // called from finalize(), enqueue(), or clear(). We
454 // need to detect when this is called from the HeapWorker
455 // context and just give up.
456 dvmSignalHeapWorker(false);
457 cc = pthread_cond_wait(&gDvm.heapWorkerIdleCond, &gDvm.heapWorkerLock);
458 assert(cc == 0);
459
460 dvmUnlockMutex(&gDvm.heapWorkerLock);
461
462 dvmChangeStatus(NULL, THREAD_RUNNING);
463}
464
465/*
466 * Do not return until any pending heap work has finished. This may
467 * or may not happen in the context of the calling thread.
468 * No exceptions will escape.
469 */
470void dvmRunFinalizationSync()
471{
472 if (gDvm.zygote) {
473 assert(!gDvm.heapWorkerReady);
474
475 /* When in zygote mode, there is no heap worker.
476 * Do the work in the current thread.
477 */
478 dvmLockMutex(&gDvm.heapWorkerLock);
479 doHeapWork(dvmThreadSelf());
480 dvmUnlockMutex(&gDvm.heapWorkerLock);
481 } else {
482 /* Outside of zygote mode, we can just ask the
483 * heap worker thread to do the work.
484 */
485 dvmWaitForHeapWorkerIdle();
486 }
487}
488
489/*
490 * Requests that dvmHeapSourceTrim() be called no sooner
491 * than timeoutSec seconds from now. If timeoutSec
492 * is zero, any pending trim is cancelled.
493 *
494 * Caller must hold heapWorkerLock.
495 */
496void dvmScheduleHeapSourceTrim(size_t timeoutSec)
497{
498 GcHeap *gcHeap = gDvm.gcHeap;
499 struct timespec timeout;
500
501 if (timeoutSec == 0) {
502 timeout.tv_sec = 0;
503 timeout.tv_nsec = 0;
504 /* Don't wake up the thread just to tell it to cancel.
505 * If it wakes up naturally, we can avoid the extra
506 * context switch.
507 */
508 } else {
509 struct timeval now;
510
Andy McFaddeneaf2ee02009-06-02 13:42:44 -0700511#ifdef HAVE_TIMEDWAIT_MONOTONIC
512 clock_gettime(CLOCK_MONOTONIC, &timeout);
513 timeout.tv_sec += timeoutSec;
514#else
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800515 gettimeofday(&now, NULL);
516 timeout.tv_sec = now.tv_sec + timeoutSec;
517 timeout.tv_nsec = now.tv_usec * 1000;
Andy McFaddeneaf2ee02009-06-02 13:42:44 -0700518#endif
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800519 dvmSignalHeapWorker(false);
520 }
521 gcHeap->heapWorkerNextTrim = timeout;
522}