• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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 package java.lang;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 import android.system.Os;
21 import android.system.OsConstants;
22 
23 import java.lang.ref.FinalizerReference;
24 import java.lang.ref.Reference;
25 import java.lang.ref.ReferenceQueue;
26 import java.util.concurrent.CountDownLatch;
27 import java.util.concurrent.TimeoutException;
28 import java.util.concurrent.atomic.AtomicInteger;
29 import libcore.util.EmptyArray;
30 
31 import dalvik.system.VMRuntime;
32 import dalvik.system.VMDebug;
33 
34 /**
35  * Calls Object.finalize() on objects in the finalizer reference queue. The VM
36  * will abort if any finalize() call takes more than the maximum finalize time
37  * to complete.
38  *
39  * @hide
40  */
41 public final class Daemons {
42     private static final int NANOS_PER_MILLI = 1000 * 1000;
43 
44     // This used to be final. IT IS NOW ONLY WRITTEN. We now update it when we look at the command
45     // line argument, for the benefit of mis-behaved apps that might read it.  SLATED FOR REMOVAL.
46     // There is no reason to use this: Finalizers should not rely on the value. If a finalizer takes
47     // appreciable time, the work should be done elsewhere.  Based on disassembly of Daemons.class,
48     // the value is effectively inlined, so changing the field never did have an effect.
49     // DO NOT USE. FOR ANYTHING. THIS WILL BE REMOVED SHORTLY.
50     @UnsupportedAppUsage
51     private static long MAX_FINALIZE_NANOS = 10L * 1000 * NANOS_PER_MILLI;
52 
53     private static final Daemon[] DAEMONS = new Daemon[] {
54             HeapTaskDaemon.INSTANCE,
55             ReferenceQueueDaemon.INSTANCE,
56             FinalizerDaemon.INSTANCE,
57             FinalizerWatchdogDaemon.INSTANCE,
58     };
59     private static final CountDownLatch POST_ZYGOTE_START_LATCH = new CountDownLatch(DAEMONS.length);
60     private static final CountDownLatch PRE_ZYGOTE_START_LATCH = new CountDownLatch(DAEMONS.length);
61 
62     private static boolean postZygoteFork = false;
63 
64     @UnsupportedAppUsage
start()65     public static void start() {
66         for (Daemon daemon : DAEMONS) {
67             daemon.start();
68         }
69     }
70 
startPostZygoteFork()71     public static void startPostZygoteFork() {
72         postZygoteFork = true;
73         for (Daemon daemon : DAEMONS) {
74             daemon.startPostZygoteFork();
75         }
76     }
77 
78     @UnsupportedAppUsage
stop()79     public static void stop() {
80         for (Daemon daemon : DAEMONS) {
81             daemon.stop();
82         }
83     }
84 
waitForDaemonStart()85     private static void waitForDaemonStart() throws Exception {
86         if (postZygoteFork) {
87             POST_ZYGOTE_START_LATCH.await();
88         } else {
89             PRE_ZYGOTE_START_LATCH.await();
90         }
91     }
92 
93     /**
94      * A background task that provides runtime support to the application.
95      * Daemons can be stopped and started, but only so that the zygote can be a
96      * single-threaded process when it forks.
97      */
98     private static abstract class Daemon implements Runnable {
99         @UnsupportedAppUsage
100         private Thread thread;
101         private String name;
102         private boolean postZygoteFork;
103 
Daemon(String name)104         protected Daemon(String name) {
105             this.name = name;
106         }
107 
108         @UnsupportedAppUsage
start()109         public synchronized void start() {
110             startInternal();
111         }
112 
startPostZygoteFork()113         public synchronized void startPostZygoteFork() {
114             postZygoteFork = true;
115             startInternal();
116         }
117 
startInternal()118         public void startInternal() {
119             if (thread != null) {
120                 throw new IllegalStateException("already running");
121             }
122             thread = new Thread(ThreadGroup.systemThreadGroup, this, name);
123             thread.setDaemon(true);
124             thread.setSystemDaemon(true);
125             thread.start();
126         }
127 
run()128         public final void run() {
129             if (postZygoteFork) {
130                 // We don't set the priority before the Thread.start() call above because
131                 // Thread.start() will call SetNativePriority and overwrite the desired native
132                 // priority. We (may) use a native priority that doesn't have a corresponding
133                 // java.lang.Thread-level priority (native priorities are more coarse-grained.)
134                 VMRuntime.getRuntime().setSystemDaemonThreadPriority();
135                 POST_ZYGOTE_START_LATCH.countDown();
136             } else {
137                 PRE_ZYGOTE_START_LATCH.countDown();
138             }
139             try {
140                 runInternal();
141             } catch (Throwable ex) {
142                 // Should never happen, but may not o.w. get reported, e.g. in zygote.
143                 // Risk logging redundantly, rather than losing it.
144                 System.logE("Uncaught exception in system thread " + name, ex);
145                 throw ex;
146             }
147         }
148 
runInternal()149         public abstract void runInternal();
150 
151         /**
152          * Returns true while the current thread should continue to run; false
153          * when it should return.
154          */
155         @UnsupportedAppUsage
isRunning()156         protected synchronized boolean isRunning() {
157             return thread != null;
158         }
159 
interrupt()160         public synchronized void interrupt() {
161             interrupt(thread);
162         }
163 
interrupt(Thread thread)164         public synchronized void interrupt(Thread thread) {
165             if (thread == null) {
166                 throw new IllegalStateException("not running");
167             }
168             thread.interrupt();
169         }
170 
171         /**
172          * Waits for the runtime thread to stop. This interrupts the thread
173          * currently running the runnable and then waits for it to exit.
174          */
175         @UnsupportedAppUsage
stop()176         public void stop() {
177             Thread threadToStop;
178             synchronized (this) {
179                 threadToStop = thread;
180                 thread = null;
181             }
182             if (threadToStop == null) {
183                 throw new IllegalStateException("not running");
184             }
185             interrupt(threadToStop);
186             while (true) {
187                 try {
188                     threadToStop.join();
189                     return;
190                 } catch (InterruptedException ignored) {
191                 } catch (OutOfMemoryError ignored) {
192                     // An OOME may be thrown if allocating the InterruptedException failed.
193                 }
194             }
195         }
196 
197         /**
198          * Returns the current stack trace of the thread, or an empty stack trace
199          * if the thread is not currently running.
200          */
getStackTrace()201         public synchronized StackTraceElement[] getStackTrace() {
202             return thread != null ? thread.getStackTrace() : EmptyArray.STACK_TRACE_ELEMENT;
203         }
204     }
205 
206     /**
207      * This heap management thread moves elements from the garbage collector's
208      * pending list to the managed reference queue.
209      */
210     private static class ReferenceQueueDaemon extends Daemon {
211         @UnsupportedAppUsage
212         private static final ReferenceQueueDaemon INSTANCE = new ReferenceQueueDaemon();
213 
214         // Monitored by FinalizerWatchdogDaemon to make sure we're still working.
215         private final AtomicInteger progressCounter = new AtomicInteger(0);
216 
ReferenceQueueDaemon()217         ReferenceQueueDaemon() {
218             super("ReferenceQueueDaemon");
219         }
220 
runInternal()221         @Override public void runInternal() {
222             FinalizerWatchdogDaemon.INSTANCE.monitoringNeeded(FinalizerWatchdogDaemon.RQ_DAEMON);
223             while (isRunning()) {
224                 Reference<?> list;
225                 try {
226                     synchronized (ReferenceQueue.class) {
227                         if (ReferenceQueue.unenqueued == null) {
228                             progressCounter.incrementAndGet();
229                             FinalizerWatchdogDaemon.INSTANCE.monitoringNotNeeded(
230                                     FinalizerWatchdogDaemon.RQ_DAEMON);
231                             do {
232                                ReferenceQueue.class.wait();
233                             } while (ReferenceQueue.unenqueued == null);
234                             progressCounter.incrementAndGet();
235                             FinalizerWatchdogDaemon.INSTANCE.monitoringNeeded(
236                                     FinalizerWatchdogDaemon.RQ_DAEMON);
237                         }
238                         list = ReferenceQueue.unenqueued;
239                         ReferenceQueue.unenqueued = null;
240                     }
241                 } catch (InterruptedException e) {
242                     continue;
243                 } catch (OutOfMemoryError e) {
244                     continue;
245                 }
246                 ReferenceQueue.enqueuePending(list, progressCounter);
247                 FinalizerWatchdogDaemon.INSTANCE.resetTimeouts();
248             }
249         }
250 
currentlyProcessing()251         ReferenceQueue currentlyProcessing() {
252           return ReferenceQueue.getCurrentQueue();
253         }
254     }
255 
256     private static class FinalizerDaemon extends Daemon {
257         @UnsupportedAppUsage
258         private static final FinalizerDaemon INSTANCE = new FinalizerDaemon();
259         private final ReferenceQueue<Object> queue = FinalizerReference.queue;
260         private final AtomicInteger progressCounter = new AtomicInteger(0);
261         // Object (not reference!) being finalized. Accesses may race!
262         @UnsupportedAppUsage
263         private Object finalizingObject = null;
264 
FinalizerDaemon()265         FinalizerDaemon() {
266             super("FinalizerDaemon");
267         }
268 
runInternal()269         @Override public void runInternal() {
270             // This loop may be performance critical, since we need to keep up with mutator
271             // generation of finalizable objects.
272             // We minimize the amount of work we do per finalizable object. For example, we avoid
273             // reading the current time here, since that involves a kernel call per object.  We
274             // limit fast path communication with FinalizerWatchDogDaemon to what's unavoidable: A
275             // non-volatile store to communicate the current finalizable object, e.g. for
276             // reporting, and a release store (lazySet) to a counter.
277             // We do stop the  FinalizerWatchDogDaemon if we have nothing to do for a
278             // potentially extended period.  This prevents the device from waking up regularly
279             // during idle times.
280 
281             // Local copy of progressCounter; saves a fence per increment on ARM and MIPS.
282             int localProgressCounter = progressCounter.get();
283 
284             FinalizerWatchdogDaemon.INSTANCE.monitoringNeeded(
285                     FinalizerWatchdogDaemon.FINALIZER_DAEMON);
286             while (isRunning()) {
287                 try {
288                     // Use non-blocking poll to avoid FinalizerWatchdogDaemon communication
289                     // when busy.
290                     FinalizerReference<?> finalizingReference = (FinalizerReference<?>)queue.poll();
291                     if (finalizingReference != null) {
292                         finalizingObject = finalizingReference.get();
293                         progressCounter.lazySet(++localProgressCounter);
294                     } else {
295                         finalizingObject = null;
296                         progressCounter.lazySet(++localProgressCounter);
297                         // Slow path; block.
298                         FinalizerWatchdogDaemon.INSTANCE.monitoringNotNeeded(
299                                 FinalizerWatchdogDaemon.FINALIZER_DAEMON);
300                         finalizingReference = (FinalizerReference<?>)queue.remove();
301                         finalizingObject = finalizingReference.get();
302                         progressCounter.set(++localProgressCounter);
303                         FinalizerWatchdogDaemon.INSTANCE.monitoringNeeded(
304                                 FinalizerWatchdogDaemon.FINALIZER_DAEMON);
305                     }
306                     doFinalize(finalizingReference);
307                 } catch (InterruptedException ignored) {
308                 } catch (OutOfMemoryError ignored) {
309                 }
310             }
311         }
312 
313         @FindBugsSuppressWarnings("FI_EXPLICIT_INVOCATION")
doFinalize(FinalizerReference<?> reference)314         private void doFinalize(FinalizerReference<?> reference) {
315             FinalizerReference.remove(reference);
316             Object object = reference.get();
317             reference.clear();
318             try {
319                 object.finalize();
320             } catch (Throwable ex) {
321                 // The RI silently swallows these, but Android has always logged.
322                 System.logE("Uncaught exception thrown by finalizer", ex);
323             } finally {
324                 // Done finalizing, stop holding the object as live.
325                 finalizingObject = null;
326             }
327         }
328     }
329 
330     /**
331      * The watchdog exits the VM if either the FinalizerDaemon, or the ReferenceQueueDaemon
332      * gets stuck. We consider the finalizer to be stuck if it spends more than
333      * MAX_FINALIZATION_MILLIS on one instance. We consider ReferenceQueueDaemon to be
334      * potentially stuck if it spends more than MAX_FINALIZATION_MILLIS processing a single
335      * Cleaner or transferring objects into a single queue, but only report if this happens
336      * a few times in a row, to compensate for the fact that multiple Cleaners may be involved.
337      */
338     private static class FinalizerWatchdogDaemon extends Daemon {
339         // Single bit values to identify daemon to be watched.
340         static final int FINALIZER_DAEMON = 1;
341         static final int RQ_DAEMON = 2;
342 
343         @UnsupportedAppUsage
344         private static final FinalizerWatchdogDaemon INSTANCE = new FinalizerWatchdogDaemon();
345 
346         private int activeWatchees;  // Only synchronized accesses.
347 
348         private long finalizerTimeoutNs = 0;  // Lazily initialized.
349 
350         // We tolerate this many timeouts during an enqueuePending call.
351         // This number is > 1, since we may only report enqueuePending progress rarely.
352         private static final int TOLERATED_REFERENCE_QUEUE_TIMEOUTS = 5;
353         private static final AtomicInteger observedReferenceQueueTimeouts = new AtomicInteger(0);
354 
FinalizerWatchdogDaemon()355         FinalizerWatchdogDaemon() {
356             super("FinalizerWatchdogDaemon");
357         }
358 
resetTimeouts()359         void resetTimeouts() {
360             observedReferenceQueueTimeouts.lazySet(0);
361         }
362 
runInternal()363         @Override public void runInternal() {
364             while (isRunning()) {
365                 if (!sleepUntilNeeded()) {
366                     // We have been interrupted, need to see if this daemon has been stopped.
367                     continue;
368                 }
369                 final TimeoutException exception = waitForProgress();
370                 if (exception != null && !VMDebug.isDebuggerConnected()) {
371                     timedOut(exception);
372                     break;
373                 }
374             }
375         }
376 
377         /**
378          * Wait until something is ready to be finalized.
379          * Return false if we have been interrupted
380          * See also http://code.google.com/p/android/issues/detail?id=22778.
381          */
sleepUntilNeeded()382         private synchronized boolean sleepUntilNeeded() {
383             while (activeWatchees == 0) {
384                 try {
385                     wait();
386                 } catch (InterruptedException e) {
387                     // Daemon.stop may have interrupted us.
388                     return false;
389                 } catch (OutOfMemoryError e) {
390                     return false;
391                 }
392             }
393             return true;
394         }
395 
396         /**
397          * Notify daemon that it's OK to sleep until notified that something is ready to be
398          * finalized.
399          */
monitoringNotNeeded(int whichDaemon)400         private synchronized void monitoringNotNeeded(int whichDaemon) {
401             activeWatchees &= ~whichDaemon;
402         }
403 
404         /**
405          * Notify daemon that there is something ready to be finalized.
406          */
monitoringNeeded(int whichDaemon)407         private synchronized void monitoringNeeded(int whichDaemon) {
408             int oldWatchees = activeWatchees;
409             activeWatchees |= whichDaemon;
410 
411             if (oldWatchees == 0) {
412                 notify();
413             }
414         }
415 
isActive(int whichDaemon)416         private synchronized boolean isActive(int whichDaemon) {
417             return (activeWatchees & whichDaemon) != 0;
418         }
419 
420         /**
421          * Sleep for the given number of nanoseconds, or slightly longer.
422          * @return false if we were interrupted.
423          */
sleepForNanos(long durationNanos)424         private boolean sleepForNanos(long durationNanos) {
425             // It's important to base this on nanoTime(), not currentTimeMillis(), since
426             // the former stops counting when the processor isn't running.
427             long startNanos = System.nanoTime();
428             while (true) {
429                 long elapsedNanos = System.nanoTime() - startNanos;
430                 long sleepNanos = durationNanos - elapsedNanos;
431                 if (sleepNanos <= 0) {
432                     return true;
433                 }
434                 // Ensure the nano time is always rounded up to the next whole millisecond,
435                 // ensuring the delay is >= the requested delay.
436                 long sleepMillis = (sleepNanos + NANOS_PER_MILLI - 1) / NANOS_PER_MILLI;
437                 try {
438                     Thread.sleep(sleepMillis);
439                 } catch (InterruptedException e) {
440                     if (!isRunning()) {
441                         return false;
442                     }
443                 } catch (OutOfMemoryError ignored) {
444                     if (!isRunning()) {
445                         return false;
446                     }
447                 }
448             }
449         }
450 
451 
452         /**
453          * Return null (normal case) or an exception describing what timed out.
454          * Wait VMRuntime.getFinalizerTimeoutMs.  If the FinalizerDaemon took essentially the
455          * whole time processing a single reference, or the ReferenceQueueDaemon failed to make
456          * visible progress during that time, return an exception.  Only called from a single
457          * thread.
458          */
waitForProgress()459         private TimeoutException waitForProgress() {
460             if (finalizerTimeoutNs == 0) {
461                 finalizerTimeoutNs =
462                         NANOS_PER_MILLI * VMRuntime.getRuntime().getFinalizerTimeoutMs();
463                 // Temporary app backward compatibility. Remove eventually.
464                 MAX_FINALIZE_NANOS = finalizerTimeoutNs;
465             }
466             boolean monitorFinalizer = false;
467             int finalizerStartCount = 0;
468             if (isActive(FINALIZER_DAEMON)) {
469                 monitorFinalizer = true;
470                 finalizerStartCount = FinalizerDaemon.INSTANCE.progressCounter.get();
471             }
472             boolean monitorRefQueue = false;
473             int refQueueStartCount = 0;
474             if (isActive(RQ_DAEMON)) {
475                 monitorRefQueue = true;
476                 refQueueStartCount = ReferenceQueueDaemon.INSTANCE.progressCounter.get();
477             }
478 
479             // Avoid remembering object being finalized, so as not to keep it alive.
480             if (!sleepForNanos(finalizerTimeoutNs)) {
481                 // Don't report possibly spurious timeout if we are interrupted.
482                 return null;
483             }
484             if (FinalizerDaemon.INSTANCE.progressCounter.get() == finalizerStartCount
485                 && monitorFinalizer && isActive(FINALIZER_DAEMON)) {
486                 // We assume that only remove() and doFinalize() may take time comparable to the
487                 // finalizer timeout.
488                 // We observed neither the effect of the monitoringNotNeeded() nor the increment
489                 // preceding a later wakeUp. Any remove() call by the FinalizerDaemon during our
490                 // sleep interval must have been followed by a monitoringNeeded() call before we
491                 // checked activeCallees.  But then we would have seen the counter increment.
492                 // Thus there cannot have been such a remove() call.
493                 // The FinalizerDaemon must not have progressed (from either the beginning or the
494                 // last progressCounter increment) to either the next increment or
495                 // monitoringNotNeeded() call.
496                 // Thus we must have taken essentially the whole finalizerTimeoutMs in a single
497                 // doFinalize() call.  Thus it's OK to time out.  finalizingObject was set just
498                 // before the counter increment, which preceded the doFinalize() call.  Thus we
499                 // are guaranteed to get the correct finalizing value below, unless doFinalize()
500                 // just finished as we were timing out, in which case we may get null or a later
501                 // one.  In this last case, we are very likely to discard it below.
502                 Object finalizing = FinalizerDaemon.INSTANCE.finalizingObject;
503                 sleepForNanos(500 * NANOS_PER_MILLI);
504                 // Recheck to make it even less likely we report the wrong finalizing object in
505                 // the case which a very slow finalization just finished as we were timing out.
506                 if (isActive(FINALIZER_DAEMON)
507                         && FinalizerDaemon.INSTANCE.progressCounter.get() == finalizerStartCount) {
508                     return finalizerTimeoutException(finalizing);
509                 }
510             }
511             if (ReferenceQueueDaemon.INSTANCE.progressCounter.get() == refQueueStartCount
512                 && monitorRefQueue && isActive(RQ_DAEMON)) {
513                 if (observedReferenceQueueTimeouts.incrementAndGet()
514                         > TOLERATED_REFERENCE_QUEUE_TIMEOUTS) {
515                     return refQueueTimeoutException(
516                             ReferenceQueueDaemon.INSTANCE.currentlyProcessing());
517                 }
518             }
519             return null;
520         }
521 
finalizerTimeoutException(Object object)522         private static TimeoutException finalizerTimeoutException(Object object) {
523             String message = object.getClass().getName() + ".finalize() timed out after "
524                     + VMRuntime.getRuntime().getFinalizerTimeoutMs() / 1000 + " seconds";
525             TimeoutException syntheticException = new TimeoutException(message);
526             // We use the stack from where finalize() was running to show where it was stuck.
527             syntheticException.setStackTrace(FinalizerDaemon.INSTANCE.getStackTrace());
528             return syntheticException;
529         }
530 
refQueueTimeoutException(ReferenceQueue rq)531         private static TimeoutException refQueueTimeoutException(ReferenceQueue rq) {
532             String message = "ReferenceQueueDaemon timed out while targeting " + rq;
533             return new TimeoutException(message);
534         }
535 
timedOut(TimeoutException exception)536         private static void timedOut(TimeoutException exception) {
537             // Send SIGQUIT to get native stack traces.
538             try {
539                 Os.kill(Os.getpid(), OsConstants.SIGQUIT);
540                 // Sleep a few seconds to let the stack traces print.
541                 Thread.sleep(5000);
542             } catch (Exception e) {
543                 System.logE("failed to send SIGQUIT", e);
544             } catch (OutOfMemoryError ignored) {
545                 // May occur while trying to allocate the exception.
546             }
547 
548             // Ideally, we'd want to do this if this Thread had no handler to dispatch to.
549             // Unfortunately, it's extremely to messy to query whether a given Thread has *some*
550             // handler to dispatch to, either via a handler set on itself, via its ThreadGroup
551             // object or via the defaultUncaughtExceptionHandler.
552             //
553             // As an approximation, we log by hand and exit if there's no pre-exception handler nor
554             // a default uncaught exception handler.
555             //
556             // Note that this condition will only ever be hit by ART host tests and standalone
557             // dalvikvm invocations. All zygote forked process *will* have a pre-handler set
558             // in RuntimeInit and they cannot subsequently override it.
559             if (Thread.getUncaughtExceptionPreHandler() == null &&
560                     Thread.getDefaultUncaughtExceptionHandler() == null) {
561                 // If we have no handler, log and exit.
562                 System.logE(exception.getMessage(), exception);
563                 System.exit(2);
564             }
565 
566             // Otherwise call the handler to do crash reporting.
567             // We don't just throw because we're not the thread that
568             // timed out; we're the thread that detected it.
569             Thread.currentThread().dispatchUncaughtException(exception);
570         }
571     }
572 
573     // Adds a heap trim task to the heap event processor, not called from java. Left for
574     // compatibility purposes due to reflection.
575     @UnsupportedAppUsage
requestHeapTrim()576     public static void requestHeapTrim() {
577         VMRuntime.getRuntime().requestHeapTrim();
578     }
579 
580     // Adds a concurrent GC request task ot the heap event processor, not called from java. Left
581     // for compatibility purposes due to reflection.
requestGC()582     public static void requestGC() {
583         VMRuntime.getRuntime().requestConcurrentGC();
584     }
585 
586     private static class HeapTaskDaemon extends Daemon {
587         private static final HeapTaskDaemon INSTANCE = new HeapTaskDaemon();
588 
HeapTaskDaemon()589         HeapTaskDaemon() {
590             super("HeapTaskDaemon");
591         }
592 
593         // Overrides the Daemon.interupt method which is called from Daemons.stop.
interrupt(Thread thread)594         public synchronized void interrupt(Thread thread) {
595             VMRuntime.getRuntime().stopHeapTaskProcessor();
596         }
597 
runInternal()598         @Override public void runInternal() {
599             synchronized (this) {
600                 if (isRunning()) {
601                   // Needs to be synchronized or else we there is a race condition where we start
602                   // the thread, call stopHeapTaskProcessor before we start the heap task
603                   // processor, resulting in a deadlock since startHeapTaskProcessor restarts it
604                   // while the other thread is waiting in Daemons.stop().
605                   VMRuntime.getRuntime().startHeapTaskProcessor();
606                 }
607             }
608             // This runs tasks until we are stopped and there is no more pending task.
609             VMRuntime.getRuntime().runHeapTasks();
610         }
611     }
612 }
613