一段时间以来,端上由 TimeoutException 造成的 crash 频发。经过全面排查日志、Crash 共性,发现 TimeoutException 基本上由 FinalizerWatchdogDaemon-> finalizerTimedOut 抛出。
一、复现问题
重写 finalize(),在方法内进行耗时操作,并重复创建该类对象。
@Override
protected void finalize() throws Throwable {
Thread.sleep(xxx);
super.finalize();
}
果然抛出了 TimeoutException 异常,且在调用链中出现了 FinalizerWatchdogDaemon.finalizerTimedOut。
注:部分机型不会抛出异常。这部分手机可能在 Rom 层做了优化。
二、寻找 hook 点
既然异常由 FinalizerWatchdogDaemon 抛出,接下来就从它入手。
-
FinalizerWatchdogDaemon 和 FinalizerDaemon 关系较为密切。当对象将被回收时,jvm 会将重写 finalize 的对象存入队列中,由 FinalizerDaemon 统一调用 Object.finalize 方法。
-
FinalizerWatchdogDaemon 顾名思义是看门狗的一类。它则负责检测 finalize 方法调用是否超时,主要逻辑在 FinalizerWatchdogDaemon.finalizerTimeOut()。
那么规避 FinalizerWatchdogDaemon 抛出的 TimeoutException 就是让系统无法走到 finalizerTimedOut 的逻辑。finalizerTimedOut 只有一处调用,即 runInternal 。调用 finalizerTimedOut 之前有一个前置条件:返回需要 finalize 的对象不为空。
runInternal()那么我们要做的是只要确保 waitForFinalization 返回对象为空即可。而 waitForFinalization 方法内部有一处校验逻辑:
if (!sleepFor(MAX_FINALIZE_NANOS)) {
// Don't report possibly spurious timeout if we are interrupted.
return null;
}
即只要耗时没有超过 MAX_FINALIZE_NANOS,返回的对象为空。
因此解决方案为:加大 MAX_FINALIZE_NANOS 的值:
try {
Class<?> clazz = Class.forName("java.lang.Daemons");
Field field = clazz.getDeclaredField("MAX_FINALIZE_NANOS");
field.setAccessible(true);
field.set(null, xxxx); // 改为确定的阀值
} catch (Exception e) {
//no-op
}
注:有些 Rom 可能修改了 MAX_FINALIZE_NANOS 值。因此最好在修改前做一个判断,满足一定条件再进行反射修改。另对于 9.0 以上系统可直接返回不做反射处理。
除了增大阀值,另外还可以直接停止 FinalizerWatchdogDaemon 监听。相对来说风险稍大。可能导致未知问题。
由于我们的 APP 体量相对还较大。综合比较,选择了增大阀值的方式解决 TimeoutException。另对于这类修改,最好还是通过灰度等方式先行确认改动是否存在问题。
网友评论