738 lines
32 KiB
Java
738 lines
32 KiB
Java
![]() |
/*
|
||
|
* 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();
|
||
|
}
|
||
|
}
|
||
|
}
|