Analysis and Mitigation of Android finalize() TimeoutException Crashes
The article examines Android crashes caused by the finalize() method exceeding its watchdog timeout, explains the underlying daemon implementation and typical causes such as long‑running finalizers, lock contention and low‑priority threads, and recommends avoiding finalize, reducing finalizable objects, or suppressing the watchdog exception as practical mitigations.
This article investigates a frequent crash in large Android applications where the finalize() method times out, resulting in a java.util.concurrent.TimeoutException such as "android.content.res.AssetManager$AssetInputStream.finalize() timed out after 10 seconds".
Typical crash stack trace:
java.util.concurrent.TimeoutException: android.content.res.AssetManager$AssetInputStream.finalize() timed out after 15 seconds
at android.content.res.AssetManager$AssetInputStream.close(AssetManager.java:559)
at android.content.res.AssetManager$AssetInputStream.finalize(AssetManager.java:592)
at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:187)
at java.lang.Daemons$FinalizerDaemon.run(Daemons.java:170)
at java.lang.Thread.run(Thread.java:841)The timeout occurs inside java.lang.Daemons$FinalizerDaemon.doFinalize . The article analyses the relevant AOSP source (Android 4.4) for the classes Daemons , FinalizerDaemon , and FinalizerWatchdogDaemon , showing how the daemon threads are started from the Zygote process and how the watchdog monitors the finalizer.
public final class Daemons {
private static final long MAX_FINALIZE_NANOS = 10L * NANOS_PER_SECOND;
public static void start() {
FinalizerDaemon.INSTANCE.start();
FinalizerWatchdogDaemon.INSTANCE.start();
}
public static void stop() {
FinalizerDaemon.INSTANCE.stop();
FinalizerWatchdogDaemon.INSTANCE.stop();
}
}Key causes identified:
Long‑running finalize() implementations (e.g., IO operations, synchronized blocks that block on AssetManager lock).
Lock contention when many resources are loaded, especially on low‑end devices.
CPU sleep during GC on Android 4.x devices, causing the measured duration to exceed the timeout.
High IO load leading to slow resource release.
Low priority of FinalizerDaemon thread, which can be pre‑empted by higher‑priority threads.
Proposed mitigation strategies:
Ideal measures
Avoid using finalize() for resource release; manage resources explicitly.
Reduce the number of finalizable objects.
Keep finalize() implementations lightweight and avoid long synchronized sections.
Limit creation of high‑priority threads that starve the finalizer.
Stop‑gap measures
Increase the timeout by reflecting into Daemons.MAX_FINALIZE_NANOS (note: ineffective after compilation because it’s a constant).
Stop the FinalizerWatchdogDaemon via reflection:
try {
Class
clazz = Class.forName("java.lang.Daemons$FinalizerWatchdogDaemon");
Method stop = clazz.getSuperclass().getDeclaredMethod("stop");
stop.setAccessible(true);
Field instance = clazz.getDeclaredField("INSTANCE");
instance.setAccessible(true);
stop.invoke(instance.get(null));
} catch (Throwable e) {
e.printStackTrace();
}This disables the watchdog, preventing the exception from being thrown, but it may break on Android 9+ where private APIs are restricted.
Practical solution – ignore the exception
Replace the default Thread.UncaughtExceptionHandler with a wrapper that swallows the TimeoutException coming from the FinalizerWatchdogDaemon thread:
Thread.UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
if ("FinalizerWatchdogDaemon".equals(t.getName()) && e instanceof TimeoutException) {
// ignore
} else {
defaultHandler.uncaughtException(t, e);
}
}
});This approach is compatible with all Android versions, minimally invasive, and keeps the app alive when the finalizer times out.
Conclusion: While the above mitigations can reduce or hide the crash, the root cause remains – a slow or blocked finalize() . Long‑term stability requires eliminating reliance on finalizers, improving code structure, and adopting proper resource management patterns to avoid OOM caused by a growing finalizer reference queue.
Didi Tech
Official Didi technology account
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.