/* * 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 dalvik.system.VMRuntime; import java.lang.ref.FinalizerReference; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.util.concurrent.TimeoutException; import libcore.util.EmptyArray; /** * 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 = 1000000; private static final long MAX_FINALIZE_MILLIS = 10L * 1000L; // 10 seconds public static void start() { ReferenceQueueDaemon.INSTANCE.start(); FinalizerDaemon.INSTANCE.start(); FinalizerWatchdogDaemon.INSTANCE.start(); } public static void stop() { ReferenceQueueDaemon.INSTANCE.stop(); FinalizerDaemon.INSTANCE.stop(); FinalizerWatchdogDaemon.INSTANCE.stop(); } /** * 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 { private Thread thread; public synchronized void start() { if (thread != null) { throw new IllegalStateException("already running"); } thread = new Thread(this, getClass().getSimpleName()); thread.setDaemon(true); thread.start(); } public abstract void run(); /** * Returns true while the current thread should continue to run; false * when it should return. */ protected synchronized boolean isRunning() { return thread != null; } public synchronized void interrupt() { 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. */ public void stop() { Thread threadToStop; synchronized (this) { threadToStop = thread; thread = null; } if (threadToStop == null) { throw new IllegalStateException("not running"); } threadToStop.interrupt(); while (true) { try { threadToStop.join(); return; } catch (InterruptedException ignored) { } } } /** * 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; } } /** * This heap management thread moves elements from the garbage collector's * pending list to the managed reference queue. */ private static class ReferenceQueueDaemon extends Daemon { private static final ReferenceQueueDaemon INSTANCE = new ReferenceQueueDaemon(); @Override public void run() { while (isRunning()) { Reference list; try { synchronized (ReferenceQueue.class) { while (ReferenceQueue.unenqueued == null) { ReferenceQueue.class.wait(); } list = ReferenceQueue.unenqueued; ReferenceQueue.unenqueued = null; } } catch (InterruptedException e) { continue; } enqueue(list); } } private void enqueue(Reference list) { while (list != null) { Reference reference; // pendingNext is owned by the GC so no synchronization is required if (list == list.pendingNext) { reference = list; reference.pendingNext = null; list = null; } else { reference = list.pendingNext; list.pendingNext = reference.pendingNext; reference.pendingNext = null; } reference.enqueueInternal(); } } } private static class FinalizerDaemon extends Daemon { private static final FinalizerDaemon INSTANCE = new FinalizerDaemon(); private final ReferenceQueue queue = FinalizerReference.queue; private volatile Object finalizingObject; private volatile long finalizingStartedNanos; @Override public void run() { while (isRunning()) { // Take a reference, blocking until one is ready or the thread should stop try { doFinalize((FinalizerReference) queue.remove()); } catch (InterruptedException ignored) { } } } @FindBugsSuppressWarnings("FI_EXPLICIT_INVOCATION") private void doFinalize(FinalizerReference reference) { FinalizerReference.remove(reference); Object object = reference.get(); reference.clear(); try { finalizingStartedNanos = System.nanoTime(); finalizingObject = object; object.finalize(); } catch (Throwable ex) { // The RI silently swallows these, but Android has always logged. System.logE("Uncaught exception thrown by finalizer", ex); } finally { finalizingObject = null; } } } /** * The watchdog exits the VM if the finalizer ever gets stuck. We consider * the finalizer to be stuck if it spends more than MAX_FINALIZATION_MILLIS * on one instance. */ private static class FinalizerWatchdogDaemon extends Daemon { private static final FinalizerWatchdogDaemon INSTANCE = new FinalizerWatchdogDaemon(); @Override public void run() { while (isRunning()) { Object object = FinalizerDaemon.INSTANCE.finalizingObject; long startedNanos = FinalizerDaemon.INSTANCE.finalizingStartedNanos; long sleepMillis = MAX_FINALIZE_MILLIS; if (object != null) { long elapsedMillis = (System.nanoTime() - startedNanos) / NANOS_PER_MILLI; sleepMillis -= elapsedMillis; } if (sleepMillis > 0) { try { Thread.sleep(sleepMillis); } catch (InterruptedException e) { continue; } } if (object == null || object != FinalizerDaemon.INSTANCE.finalizingObject || VMRuntime.getRuntime().isDebuggerActive()) { continue; } // The current object has exceeded the finalization deadline; abort! long elapsedMillis = (System.nanoTime() - startedNanos) / NANOS_PER_MILLI; Exception syntheticException = new TimeoutException(); syntheticException.setStackTrace(FinalizerDaemon.INSTANCE.getStackTrace()); System.logE(object.getClass().getName() + ".finalize() timed out after " + elapsedMillis + " ms; limit is " + MAX_FINALIZE_MILLIS + " ms", syntheticException); System.exit(2); } } } }