TimeoutException crash 整治

一段时间以来,端上由 TimeoutException 造成的 crash 频发。经过全面排查日志、Crash 共性,发现 TimeoutException 基本上由 FinalizerWatchdogDaemon-> finalizerTimedOut 抛出。

一、复现问题

重写 finalize(),在方法内进行耗时操作,并重复创建该类对象。

 @Override
 protected void finalize() throws Throwable {
    Thread.sleep(xxx);
    super.finalize();
 }

果然抛出了 TimeoutException 异常,且在调用链中出现了 FinalizerWatchdogDaemon.finalizerTimedOut。

注:部分机型不会抛出异常。这部分手机可能在 Rom 层做了优化。

二、寻找 hook 点

既然异常由 FinalizerWatchdogDaemon 抛出,接下来就从它入手。

  1. FinalizerWatchdogDaemon 和 FinalizerDaemon 关系较为密切。当对象将被回收时,jvm 会将重写 finalize 的对象存入队列中,由 FinalizerDaemon 统一调用 Object.finalize 方法。

  2. FinalizerWatchdogDaemon 顾名思义是看门狗的一类。它则负责检测 finalize 方法调用是否超时,主要逻辑在 FinalizerWatchdogDaemon.finalizerTimeOut()。

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。另对于这类修改,最好还是通过灰度等方式先行确认改动是否存在问题。

你可能感兴趣的:(TimeoutException crash 整治)