blob: b4a2d0ea5f04bf6a1c3d53e8636247ede8ac9806 [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 */
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
The Android Open Source Project99409882009-03-18 22:20:24 -0700114 * amount of time inside a finalizer.
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800115 *
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
The Android Open Source Project99409882009-03-18 22:20:24 -0700135 if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT &&
136 (gDvm.debuggerActive || gDvm.nativeDebuggerActive))
137 {
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800138 /*
139 * Debugger suspension can block the thread indefinitely. For
140 * best results we should reset this explicitly whenever the
The Android Open Source Project99409882009-03-18 22:20:24 -0700141 * 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.
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800145 */
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}