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