/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package java.lang;

import android.compat.annotation.UnsupportedAppUsage;
import android.system.Os;
import android.system.OsConstants;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.ref.Cleaner;
import java.lang.ref.FinalizerReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import libcore.util.EmptyArray;

import dalvik.system.VMRuntime;
import dalvik.system.VMDebug;

import jdk.internal.ref.CleanerImpl;

/**
 * Calls Object.finalize() on objects in the finalizer reference queue. The VM
 * will abort if any finalize() call takes more than the maximum finalize time
 * to complete.
 *
 * @hide
 */
public final class Daemons {
    private static final int NANOS_PER_MILLI = 1000 * 1000;

    // This used to be final. IT IS NOW ONLY WRITTEN. We now update it when we look at the command
    // line argument, for the benefit of mis-behaved apps that might read it.  SLATED FOR REMOVAL.
    // There is no reason to use this: Finalizers should not rely on the value. If a finalizer takes
    // appreciable time, the work should be done elsewhere.  Based on disassembly of Daemons.class,
    // the value is effectively inlined, so changing the field never did have an effect.
    // DO NOT USE. FOR ANYTHING. THIS WILL BE REMOVED SHORTLY.
    @UnsupportedAppUsage
    private static long MAX_FINALIZE_NANOS = 10L * 1000 * NANOS_PER_MILLI;

    private static final Daemon[] DAEMONS = new Daemon[] {
            HeapTaskDaemon.INSTANCE,
            ReferenceQueueDaemon.INSTANCE,
            FinalizerDaemon.INSTANCE,
            FinalizerWatchdogDaemon.INSTANCE,
    };
    private static CountDownLatch zygoteStartLatch;

    private static boolean postZygoteFork = false;

    @UnsupportedAppUsage
    public static void start() {
        zygoteStartLatch = new CountDownLatch(DAEMONS.length);
        for (Daemon daemon : DAEMONS) {
            daemon.start();
        }
    }

    public static void startPostZygoteFork() {
        postZygoteFork = true;
        start();
    }

    @UnsupportedAppUsage
    public static void stop() {
        for (Daemon daemon : DAEMONS) {
            daemon.stop();
        }
    }

    private static void waitForDaemonStart() throws Exception {
        zygoteStartLatch.await();
    }

    /**
     * A background task that provides runtime support to the application.
     * Daemons can be stopped and started, but only so that the zygote can be a
     * single-threaded process when it forks.
     */
    private static abstract class Daemon implements Runnable {
        @UnsupportedAppUsage
        private Thread thread;
        private String name;

        protected Daemon(String name) {
            this.name = name;
        }

        @UnsupportedAppUsage
        public synchronized void start() {
            startInternal();
        }

        public void startInternal() {
            if (thread != null) {
                throw new IllegalStateException("already running");
            }
            thread = new Thread(ThreadGroup.systemThreadGroup, this, name);
            thread.setDaemon(true);
            thread.setSystemDaemon(true);
            thread.start();
        }

        public final void run() {
            if (postZygoteFork) {
                // We don't set the priority before the Thread.start() call above because
                // Thread.start() will call SetNativePriority and overwrite the desired native
                // priority. We (may) use a native priority that doesn't have a corresponding
                // java.lang.Thread-level priority (native priorities are more coarse-grained.)
                VMRuntime.getRuntime().setSystemDaemonThreadPriority();
            }
            zygoteStartLatch.countDown();
            try {
                runInternal();
                // This thread is about to exit, and we may have to wait for it to do so.
                // Terminate the underlying system thread as quickly as possible.
                // Mirroring setSystemDaemonThreadPriority, we only touch the native priority,
                // bypassing the rest of setPriority().
                Thread.currentThread().setPriority0(Thread.MAX_PRIORITY);
            } catch (Throwable ex) {
                // Usually caught in runInternal. May not o.w. get reported, e.g. in zygote.
                // Risk logging redundantly, rather than losing it.
                System.logE("Uncaught exception in system thread " + name, ex);
                throw ex;
            }
        }

        /*
         * Do the actual work. Returns normally when asked to stop.
         */
        public abstract void runInternal();

        /**
         * Returns true while the current thread should continue to run; false
         * when it should return.
         */
        @UnsupportedAppUsage
        protected synchronized boolean isRunning() {
            return thread != null;
        }

        public synchronized void interrupt() {
            interrupt(thread);
        }

        public synchronized void interrupt(Thread thread) {
            if (thread == null) {
                throw new IllegalStateException("not running");
            }
            thread.interrupt();
        }

        /**
         * Waits for the runtime thread to stop. This interrupts the thread
         * currently running the runnable and then waits for it to exit.
         */
        @UnsupportedAppUsage
        public void stop() {
            // This can be called on shutdown with the GC already disabled.
            // Allocation either here or while handling the request in the
            // daemon thread should be minimized.
            Thread threadToStop;
            synchronized (this) {
                threadToStop = thread;
                thread = null;
            }
            if (threadToStop == null) {
                throw new IllegalStateException("not running");
            }
            interrupt(threadToStop);
            while (true) {
                try {
                    threadToStop.join();
                    return;
                } catch (InterruptedException ignored) {
                } catch (OutOfMemoryError ignored) {
                    // An OOME may be thrown if allocating the InterruptedException failed.
                }
            }
        }

        /**
         * Returns the current stack trace of the thread, or an empty stack trace
         * if the thread is not currently running.
         */
        public synchronized StackTraceElement[] getStackTrace() {
            return thread != null ? thread.getStackTrace() : EmptyArray.STACK_TRACE_ELEMENT;
        }
    }

    // Allocate these strings on start-up.
    // Don't declare them private, to minimize chances that the compiler can defer allocation.
    /**
     * @hide
     */
    public static final String FD_OOM_MESSAGE = "Ignoring unexpected OOME in FinalizerDaemon";
    /**
     * @hide
     */
    public static final String RQD_OOM_MESSAGE = "Ignoring unexpected OOME in ReferenceQueueDaemon";

    /**
     * This heap management thread moves elements from the garbage collector's
     * pending list to the managed reference queue.
     */
    private static class ReferenceQueueDaemon extends Daemon {
        @UnsupportedAppUsage
        private static final ReferenceQueueDaemon INSTANCE = new ReferenceQueueDaemon();

        // Monitored by FinalizerWatchdogDaemon to make sure we're still working.
        private final AtomicInteger progressCounter = new AtomicInteger(0);

        ReferenceQueueDaemon() {
            super("ReferenceQueueDaemon");
        }

        @Override public void runInternal() {
            FinalizerWatchdogDaemon.INSTANCE.monitoringNeeded(FinalizerWatchdogDaemon.RQ_DAEMON);

            // Call once early to reduce later allocation, and hence chance of OOMEs.
            FinalizerWatchdogDaemon.INSTANCE.resetTimeouts();

            while (isRunning()) {
                Reference<?> list;
                try {
                    synchronized (ReferenceQueue.class) {
                        if (ReferenceQueue.unenqueued == null) {
                            FinalizerWatchdogDaemon.INSTANCE.monitoringNotNeeded(
                                    FinalizerWatchdogDaemon.RQ_DAEMON);
                            // Increment after above call. If watchdog saw it active, it should see
                            // the counter update.
                            progressCounter.incrementAndGet();
                            do {
                               ReferenceQueue.class.wait();
                            } while (ReferenceQueue.unenqueued == null);
                            progressCounter.incrementAndGet();
                            FinalizerWatchdogDaemon.INSTANCE.monitoringNeeded(
                                    FinalizerWatchdogDaemon.RQ_DAEMON);
                        }
                        list = ReferenceQueue.unenqueued;
                        ReferenceQueue.unenqueued = null;
                    }
                    ReferenceQueue.enqueuePending(list, progressCounter);
                    FinalizerWatchdogDaemon.INSTANCE.resetTimeouts();
                } catch (InterruptedException e) {
                    // Happens when we are asked to stop.
                } catch (OutOfMemoryError ignored) {
                    // Very unlikely. Cleaner.clean OOMEs are caught elsewhere, and nothing else
                    // should allocate regularly. Could result in enqueuePending dropping
                    // references. Does occur in tests that run out of memory.
                    System.logW(RQD_OOM_MESSAGE);
                }
            }
        }

        Object currentlyProcessing() {
          return ReferenceQueue.getCurrentTarget();
        }
    }

    private static class FinalizerDaemon extends Daemon {
        @UnsupportedAppUsage
        private static final FinalizerDaemon INSTANCE = new FinalizerDaemon();
        private final ReferenceQueue<Object> queue = FinalizerReference.queue;
        private final AtomicInteger progressCounter = new AtomicInteger(0);
        // Object (not reference!) being finalized. Accesses may race!
        @UnsupportedAppUsage
        private Object finalizingObject = null;

        // Track if we are currently logging an exception. We don't want to time out
        // in the middle.
        public static int NONE = 0;
        public static int LOGGING = 1;
        public static int TIMED_OUT = 2;
        public volatile int exceptionLoggingState = NONE;

        FinalizerDaemon() {
            super("FinalizerDaemon");
        }

        @Override public void runInternal() {
            // This loop may be performance critical, since we need to keep up with mutator
            // generation of finalizable objects.
            // We minimize the amount of work we do per finalizable object. For example, we avoid
            // reading the current time here, since that involves a kernel call per object.  We
            // limit fast path communication with FinalizerWatchDogDaemon to what's unavoidable: A
            // non-volatile store to communicate the current finalizable object, e.g. for
            // reporting, and a release store (lazySet) to a counter.
            // We do stop the  FinalizerWatchDogDaemon if we have nothing to do for a
            // potentially extended period.  This prevents the device from waking up regularly
            // during idle times.

            // Local copy of progressCounter; saves a fence per increment on ARM.
            int localProgressCounter = progressCounter.get();

            FinalizerWatchdogDaemon.INSTANCE.monitoringNeeded(
                    FinalizerWatchdogDaemon.FINALIZER_DAEMON);
            while (isRunning()) {
                try {
                    // Use non-blocking poll to avoid FinalizerWatchdogDaemon communication
                    // when busy.
                    Object nextReference = queue.poll();
                    if (nextReference != null) {
                        progressCounter.lazySet(++localProgressCounter);
                        processReference(nextReference);
                    } else {
                        finalizingObject = null;
                        // Slow path; block.
                        FinalizerWatchdogDaemon.INSTANCE.monitoringNotNeeded(
                                FinalizerWatchdogDaemon.FINALIZER_DAEMON);
                        // Increment after above call. If watchdog saw it active, it should see
                        // the counter update.
                        progressCounter.set(++localProgressCounter);
                        nextReference = queue.remove();
                        progressCounter.set(++localProgressCounter);
                        FinalizerWatchdogDaemon.INSTANCE.monitoringNeeded(
                                FinalizerWatchdogDaemon.FINALIZER_DAEMON);
                        processReference(nextReference);
                    }
                } catch (InterruptedException e) {
                    // Happens when we are asked to stop.
                } catch (OutOfMemoryError ignored) {
                    // An  OOME here is unlikely to be actionable. Bravely/foolishly continue.
                    System.logW(FD_OOM_MESSAGE);
                }
            }
        }

        private void processReference(Object ref) {
            if (ref instanceof FinalizerReference finalizingReference) {
                finalizingObject = finalizingReference.get();
                try {
                    doFinalize(finalizingReference);
                } finally {
                    // Make really sure we delay any PhantomReference enqueueing until we are
                    // really done. Possibly redundant, but the rules are complex.
                    Reference.reachabilityFence(finalizingObject);
                }
            } else if (ref instanceof Cleaner.Cleanable cleanableReference) {
                finalizingObject = cleanableReference;
                doClean(cleanableReference);
            } else {
                throw new AssertionError("Unknown class was placed into queue: " + ref);
            }
        }

        @FindBugsSuppressWarnings("FI_EXPLICIT_INVOCATION")
        private void doFinalize(FinalizerReference<?> reference) {
            FinalizerReference.remove(reference);
            Object object = reference.get();
            reference.clear();
            try {
                object.finalize();
            } catch (Throwable ex) {
                // The RI silently swallows these, but Android has always logged.
                exceptionLoggingState = LOGGING;
                System.logE("Uncaught exception thrown by finalizer", ex);
                if (exceptionLoggingState == TIMED_OUT) {
                  // We would have timed out. Attempt to crash the process here to leave a trace.
                  throw new AssertionError("Timed out logging finalizer exception", ex);
                }
            } finally {
                // Done finalizing, stop holding the object as live.
                finalizingObject = null;
                exceptionLoggingState = NONE;
            }
        }

        private void doClean(Cleaner.Cleanable cleanable) {
            try {
                cleanable.clean();
                // We only get here for SystemCleaner, and are thus not constrained to ignore
                // exceptions/errors.
            } finally {
                finalizingObject = null;
            }
        }
    }

    /**
     * The watchdog exits the VM if either the FinalizerDaemon, or the ReferenceQueueDaemon
     * gets stuck. We consider the finalizer to be stuck if it spends more than
     * MAX_FINALIZATION_MILLIS on one instance. We consider ReferenceQueueDaemon to be
     * potentially stuck if it spends more than MAX_FINALIZATION_MILLIS processing a single
     * Cleaner or transferring objects into a single queue, but only report if this happens
     * a few times in a row, to compensate for the fact that multiple Cleaners may be involved.
     */
    private static class FinalizerWatchdogDaemon extends Daemon {
        // Single bit values to identify daemon to be watched.
        static final int FINALIZER_DAEMON = 1;
        static final int RQ_DAEMON = 2;

        @UnsupportedAppUsage
        private static final FinalizerWatchdogDaemon INSTANCE = new FinalizerWatchdogDaemon();
        private static final VarHandle VH_ACTION;
        static {
            try {
                VH_ACTION = MethodHandles
                        .privateLookupIn(
                                CleanerImpl.PhantomCleanableRef.class, MethodHandles.lookup())
                        .findVarHandle(
                                CleanerImpl.PhantomCleanableRef.class, "action", Runnable.class);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                throw new AssertionError("PhantomCleanableRef should have action field", e);
            }
        }

        private int activeWatchees;  // Only synchronized accesses.

        private long finalizerTimeoutNs = 0;  // Lazily initialized.

        // We tolerate this many timeouts during an enqueuePending call.
        // This number is > 1, since we may only report enqueuePending progress rarely.
        private static final int TOLERATED_REFERENCE_QUEUE_TIMEOUTS = 5;
        private static final AtomicInteger observedReferenceQueueTimeouts = new AtomicInteger(0);

        FinalizerWatchdogDaemon() {
            super("FinalizerWatchdogDaemon");
        }

        void resetTimeouts() {
            observedReferenceQueueTimeouts.lazySet(0);
        }

        @Override public void runInternal() {
            while (isRunning()) {
                if (!sleepUntilNeeded()) {
                    // We have been interrupted, need to see if this daemon has been stopped.
                    continue;
                }
                final TimeoutException exception = waitForProgress();
                if (exception != null && !VMDebug.isDebuggerConnected()) {
                    timedOut(exception);
                    break;
                }
            }
        }

        /**
         * Wait until something is ready to be finalized.
         * Return false if we have been interrupted
         * See also http://code.google.com/p/android/issues/detail?id=22778.
         */
        private synchronized boolean sleepUntilNeeded() {
            while (activeWatchees == 0) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    // Daemon.stop may have interrupted us.
                    return false;
                } catch (OutOfMemoryError e) {
                    return false;
                }
            }
            return true;
        }

        /**
         * Notify daemon that it's OK to sleep until notified that something is ready to be
         * finalized.
         */
        private synchronized void monitoringNotNeeded(int whichDaemon) {
            activeWatchees &= ~whichDaemon;
        }

        /**
         * Notify daemon that there is something ready to be finalized.
         */
        private synchronized void monitoringNeeded(int whichDaemon) {
            int oldWatchees = activeWatchees;
            activeWatchees |= whichDaemon;

            if (oldWatchees == 0) {
                notify();
            }
        }

        private synchronized boolean isActive(int whichDaemon) {
            return (activeWatchees & whichDaemon) != 0;
        }

        /**
         * Sleep for the given number of nanoseconds, or slightly longer.
         * @return false if we were interrupted.
         */
        private boolean sleepForNanos(long durationNanos) {
            // It's important to base this on nanoTime(), not currentTimeMillis(), since
            // the former stops counting when the processor isn't running.
            long startNanos = System.nanoTime();
            while (true) {
                long elapsedNanos = System.nanoTime() - startNanos;
                long sleepNanos = durationNanos - elapsedNanos;
                if (sleepNanos <= 0) {
                    return true;
                }
                // Ensure the nano time is always rounded up to the next whole millisecond,
                // ensuring the delay is >= the requested delay.
                long sleepMillis = (sleepNanos + NANOS_PER_MILLI - 1) / NANOS_PER_MILLI;
                try {
                    Thread.sleep(sleepMillis);
                } catch (InterruptedException e) {
                    if (!isRunning()) {
                        return false;
                    }
                } catch (OutOfMemoryError ignored) {
                    if (!isRunning()) {
                        return false;
                    }
                }
            }
        }


        /**
         * Return null (normal case) or an exception describing what timed out.
         * Wait VMRuntime.getFinalizerTimeoutMs.  If the FinalizerDaemon took essentially the
         * whole time processing a single reference, or the ReferenceQueueDaemon failed to make
         * visible progress during that time, return an exception.  Only called from a single
         * thread.
         */
        private TimeoutException waitForProgress() {
            if (finalizerTimeoutNs == 0) {
                finalizerTimeoutNs =
                        NANOS_PER_MILLI * VMRuntime.getRuntime().getFinalizerTimeoutMs();
                // Temporary app backward compatibility. Remove eventually.
                MAX_FINALIZE_NANOS = finalizerTimeoutNs;
            }
            // Read the counter before we read the "active" state the first time, and after
            // we read it the last time, to guarantee that if the state was ever inactive,
            // we'll see a changed counter.
            int finalizerStartCount = FinalizerDaemon.INSTANCE.progressCounter.get();
            boolean monitorFinalizer = isActive(FINALIZER_DAEMON);
            int refQueueStartCount = ReferenceQueueDaemon.INSTANCE.progressCounter.get();
            boolean monitorRefQueue = isActive(RQ_DAEMON);
            // Avoid remembering object being finalized, so as not to keep it alive.
            final long startMillis = System.currentTimeMillis();
            final long startNanos = System.nanoTime();

            // Rather than just sleeping for finalizerTimeoutNs and checking whether we made
            // progress, we sleep repeatedly. This means that if our process makes no progress,
            // e.g. because it is frozen, the watchdog also won't, making it less likely we will
            // spuriously time out. It does mean that in the normal case, we will go to sleep
            // and wake up twice per timeout period, rather than once.
            final int NUM_WAKEUPS = 5;
            for (int i = 1; i <= NUM_WAKEUPS; ++i) {
                if (!sleepForNanos(finalizerTimeoutNs / NUM_WAKEUPS)) {
                    // Don't report possibly spurious timeout if we are interrupted.
                    return null;
                }
                if (monitorFinalizer && isActive(FINALIZER_DAEMON)
                    && FinalizerDaemon.INSTANCE.progressCounter.get() == finalizerStartCount) {
                    // Still working on same finalizer or Java 9 Cleaner.
                    continue;
                }
                if (monitorRefQueue && isActive(RQ_DAEMON)
                    && ReferenceQueueDaemon.INSTANCE.progressCounter.get() == refQueueStartCount) {
                    // Still working on same ReferenceQueue or sun.misc.Cleaner.
                    continue;
                }
                // Everything that could make progress, already did. Just sleep for the rest of the
                // timeout interval.
                if (i < NUM_WAKEUPS) {
                    sleepForNanos((finalizerTimeoutNs  / NUM_WAKEUPS) * (NUM_WAKEUPS - i));
                    return null;
                }
            }
            // Either a state change to inactive, or a task completion would have caused us to see a
            // counter change. Thus at least one of the daemons appears stuck.
            if (monitorFinalizer && isActive(FINALIZER_DAEMON)
                && FinalizerDaemon.INSTANCE.progressCounter.get() == finalizerStartCount) {
                if (FinalizerDaemon.INSTANCE.exceptionLoggingState == FinalizerDaemon.LOGGING) {
                    // Try to let it finish and crash. We will time out if we get here again.
                    FinalizerDaemon.INSTANCE.exceptionLoggingState = FinalizerDaemon.TIMED_OUT;
                }
                // The finalizingObject field was set just before the counter increment, which
                // preceded the doFinalize() or doClean() call.  Thus we are guaranteed to get the
                // correct finalizing value below, unless doFinalize() just finished as we were
                // timing out, in which case we may get null or a later one.
                Object finalizing = FinalizerDaemon.INSTANCE.finalizingObject;
                System.logE("Was finalizing " + finalizingObjectAsString(finalizing)
                    + ", now finalizing "
                    + finalizingObjectAsString(FinalizerDaemon.INSTANCE.finalizingObject));
                // Print both time of day and monotonic time differences:
                System.logE("Total elapsed millis: "
                    + (System.currentTimeMillis() - startMillis));
                System.logE("Total elapsed nanos: " + (System.nanoTime() - startNanos));
                return finalizerTimeoutException(finalizing);
            }
            if (monitorRefQueue && isActive(RQ_DAEMON)
                && ReferenceQueueDaemon.INSTANCE.progressCounter.get() == refQueueStartCount) {
                // Report RQD timeouts only if they occur repeatedly.
                // TODO: Consider changing that, but we have historically been more tolerant here,
                // since we may not increment the reference counter for every processed queue
                // element.
                Object current = ReferenceQueueDaemon.INSTANCE.currentlyProcessing();
                String currentTarget = current == null ? "unknown" : current.toString();
                System.logE("ReferenceQueueDaemon timed out while targeting " + currentTarget
                        + ". Total nanos: " + (System.nanoTime() - startNanos));
                if (observedReferenceQueueTimeouts.incrementAndGet()
                        > TOLERATED_REFERENCE_QUEUE_TIMEOUTS) {
                    return refQueueTimeoutException(currentTarget);
                }
            }
            return null;
        }

        private static TimeoutException finalizerTimeoutException(Object object) {
            if (object == null) {
                return new TimeoutException("Unknown finalizer timed out");
            }
            StringBuilder messageBuilder = new StringBuilder();

            if (object instanceof Cleaner.Cleanable) {
                messageBuilder.append(VH_ACTION.get(object).getClass().getName());
            } else {
                messageBuilder.append(object.getClass().getName()).append(".finalize()");
            }

            messageBuilder.append(" timed out after ")
                    .append(VMRuntime.getRuntime().getFinalizerTimeoutMs() / 1000)
                    .append(" seconds");
            TimeoutException syntheticException = new TimeoutException(messageBuilder.toString());
            // We use the stack from where finalize() was running to show where it was stuck.
            syntheticException.setStackTrace(FinalizerDaemon.INSTANCE.getStackTrace());
            return syntheticException;
        }

        private static String finalizingObjectAsString(Object obj) {
            if (obj == null) {
                return "unknown";
            }
            if (obj instanceof Cleaner.Cleanable) {
                return VH_ACTION.get(obj).toString();
            } else {
                return obj.toString();
            }
        }

        private static TimeoutException refQueueTimeoutException(String target) {
            String message = "ReferenceQueueDaemon timed out while targeting " + target;
            return new TimeoutException(message);
        }

        private static void timedOut(TimeoutException exception) {
            // Send SIGQUIT to get native stack traces.
            try {
                Os.kill(Os.getpid(), OsConstants.SIGQUIT);
                // Sleep a few seconds to let the stack traces print.
                Thread.sleep(5000);
            } catch (Exception e) {
                System.logE("failed to send SIGQUIT", e);
            } catch (OutOfMemoryError ignored) {
                // May occur while trying to allocate the exception.
            }

            // Ideally, we'd want to do this if this Thread had no handler to dispatch to.
            // Unfortunately, it's extremely to messy to query whether a given Thread has *some*
            // handler to dispatch to, either via a handler set on itself, via its ThreadGroup
            // object or via the defaultUncaughtExceptionHandler.
            //
            // As an approximation, we log by hand and exit if there's no pre-exception handler nor
            // a default uncaught exception handler.
            //
            // Note that this condition will only ever be hit by ART host tests and standalone
            // dalvikvm invocations. All zygote forked process *will* have a pre-handler set
            // in RuntimeInit and they cannot subsequently override it.
            if (Thread.getUncaughtExceptionPreHandler() == null &&
                    Thread.getDefaultUncaughtExceptionHandler() == null) {
                // If we have no handler, log and exit.
                System.logE(exception.getMessage(), exception);
                System.exit(2);
            }

            // Otherwise call the handler to do crash reporting.
            // We don't just throw because we're not the thread that
            // timed out; we're the thread that detected it.
            Thread.currentThread().dispatchUncaughtException(exception);
        }
    }

    // Adds a heap trim task to the heap event processor, not called from java. Left for
    // compatibility purposes due to reflection.
    @UnsupportedAppUsage
    public static void requestHeapTrim() {
        VMRuntime.getRuntime().requestHeapTrim();
    }

    // Adds a concurrent GC request task ot the heap event processor, not called from java. Left
    // for compatibility purposes due to reflection.
    public static void requestGC() {
        VMRuntime.getRuntime().requestConcurrentGC();
    }

    private static class HeapTaskDaemon extends Daemon {
        private static final HeapTaskDaemon INSTANCE = new HeapTaskDaemon();

        HeapTaskDaemon() {
            super("HeapTaskDaemon");
        }

        // Overrides the Daemon.interupt method which is called from Daemons.stop.
        public synchronized void interrupt(Thread thread) {
            VMRuntime.getRuntime().stopHeapTaskProcessor();
        }

        @Override public void runInternal() {
            synchronized (this) {
                if (isRunning()) {
                  // Needs to be synchronized or else we there is a race condition where we start
                  // the thread, call stopHeapTaskProcessor before we start the heap task
                  // processor, resulting in a deadlock since startHeapTaskProcessor restarts it
                  // while the other thread is waiting in Daemons.stop().
                  VMRuntime.getRuntime().startHeapTaskProcessor();
                }
            }
            // This runs tasks until we are stopped and there is no more pending task.
            VMRuntime.getRuntime().runHeapTasks();
        }
    }
}
