/* * 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 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(); } } }