首先,先向大家道歉:上一篇关于finalize() timed out的博客误导了大家,当问题出现后,我们应该找到问题的根本原因,从根源上去解决。然而对于这个问题来说却不太容易实现,和其他问题不同,这类问题原因比较复杂,有系统原因,也有 APP 自身的原因,比较难以定位,也难以系统性解决。探索的过程不会是一帆风顺的,我会努力提高自己对技术探索的严谨性,尽量避免此类错误,提高博客质量,和大家一起进步。
背景:18年6月份以后我们产品线的crash平台莫名出现了大量的TimeoutExceptions,crash的路径页面全部为空,log日志里面有多次的gc操作;一开始以为是网络请求方面的问题导致的,然后仔细排查了工程代码,也没有发现可以点。这个问题一直悬了好久,期间从问题根源角度尝试了各种办法,然后求证,最终找到了可行的方案,与大家分享。下面开始进入正题,问题分析:
问题详情
finalize() TimeoutException
发生在很多类中,典型的 Crash 堆栈如:
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)
这类 Crash 都是发生在 java.lang.Daemons$FinalizerDaemon.doFinalize
方法中,直接原因是对象的 finalize()
方法执行超时。系统版本从 Android 4.x 版本到 8.1 版本都有分布,低版本分布较多,出错的类有系统的类,也有我们自己的类。由于该问题在 4.x 版本中最具有代表性,下面我们将基于 AOSP 4.4 源码进行分析:
源码分析
首先从 Daemons
和 FinalizerDaemon
的由来开始分析,Daemons
开始于 Zygote
进程:Zygote
创建新进程后,通过 ZygoteHooks
类调用了 Daemons
类的 start()
方法,在 start()
方法中启动了 FinalizerDaemon
,FinalizerWatchdogDaemon
等关联的守护线程。
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();
...
}
}
Daemons
类主要处理 GC 相关操作,start()
方法调用时启动了 5 个守护线程,其中有 2 个守护线程和这个 BUG 具有直接的关系。
FinalizerDaemon 析构守护线程
对于重写了成员函数finalize()
的类,在对象创建时会新建一个 FinalizerReference
对象,这个对象封装了原对象。当原对象没有被其他对象引用时,这个对象不会被 GC 马上清除掉,而是被放入 FinalizerReference
的链表中。FinalizerDaemon
线程循环取出链表里面的对象,执行它们的 finalize()
方法,并且清除和对应 FinalizerReference
对象引用关系,对应的 FinalizerReference
对象在下次执行 GC 时就会被清理掉。
private static class FinalizerDaemon extends Daemon {
...
@Override public void run() {
while (isRunning()) {
// Take a reference, blocking until one is ready or the thread should stop
try {
doFinali