• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
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 
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  */
dvmInitializeHeapWorkerState()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  */
dvmHeapWorkerStartup(void)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         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  */
dvmHeapWorkerShutdown(void)90 void 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
115  * amount of time inside a finalizer.
116  *
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  */
dvmAssertHeapWorkerThreadRunning()122 void 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 
136         if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT &&
137             (gDvm.debuggerActive || gDvm.nativeDebuggerActive))
138         {
139             /*
140              * Debugger suspension can block the thread indefinitely.  For
141              * best results we should reset this explicitly whenever the
142              * 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.
146              */
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 
callMethod(Thread * self,Object * obj,Method * method)173 static 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  */
doHeapWork(Thread * self)228 static 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  */
heapWorkerThreadStart(void * arg)315 static 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) {
346             struct timespec now;
347 
348 #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 
357             if (trimtime.tv_sec < now.tv_sec ||
358                 (trimtime.tv_sec == now.tv_sec &&
359                  trimtime.tv_nsec <= now.tv_nsec))
360             {
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) {
389 #ifdef HAVE_TIMEDWAIT_MONOTONIC
390             cc = pthread_cond_timedwait_monotonic(&gDvm.heapWorkerCond,
391                     &gDvm.heapWorkerLock, &trimtime);
392 #else
393             cc = pthread_cond_timedwait(&gDvm.heapWorkerCond,
394                     &gDvm.heapWorkerLock, &trimtime);
395 #endif
396             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  */
dvmSignalHeapWorker(bool shouldLock)422 void 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  */
dvmWaitForHeapWorkerIdle()441 void 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  */
dvmRunFinalizationSync()470 void 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  */
dvmScheduleHeapSourceTrim(size_t timeoutSec)496 void 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 
511 #ifdef HAVE_TIMEDWAIT_MONOTONIC
512         clock_gettime(CLOCK_MONOTONIC, &timeout);
513         timeout.tv_sec += timeoutSec;
514 #else
515         gettimeofday(&now, NULL);
516         timeout.tv_sec = now.tv_sec + timeoutSec;
517         timeout.tv_nsec = now.tv_usec * 1000;
518 #endif
519         dvmSignalHeapWorker(false);
520     }
521     gcHeap->heapWorkerNextTrim = timeout;
522 }
523