有时会遇到以下崩溃:
The error is a variation of: "com.android.internal.BinderInternal$GcWatcher.finalize() timed out after 10 seconds"
java.util.concurrent.TimeoutException: android.os.BinderProxy.finalize() timed out after 10 seconds
at android.os.BinderProxy.destroy(Native Method)
at android.os.BinderProxy.finalize(Binder.java:459)
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)
它其实是发生在 GcWatcher.finalize, BinderProxy.finalize 和 PlainSocketImpl.finalize 中的一类TimeoutExceptions。这个异常90%都是发生在4.3、4.4的android系统上。
这个问题的根源在于设备会'Goes to Sleep'一会儿,就是说操作系统会通过熄屏、降低cpu循环等方式降低电量消耗,进入休眠状态。它是通过在内核层暂停进程的方式来实现的。这可能发生在常规app运行的过程中, 但是会停在一次内核调用上,比如内核层的上下文切换。这就是Dalvik GC参与最初所说TimeoutExceptions问题的方式。
Dalvik GC的基本工作方式就是,在GC循环中将收集到的一系列的对象去销毁,其主要流程可以描述为:
- take starting_timestamp,
- remove object for list of objects to release,
- release object - finalize() and call native destroy() if required,
- take end_timestamp,
- calculate (end_timestamp - starting_timestamp) and compare against a hard coded timeout value of 10 seconds,
- if timeout has reached - throw the concurrent.TimeoutException
and kill the process.
现在思考下接下来的这个场景:
有一个后台运行的进程,在运行过程中,对象被创建、使用以及被收集(以释放内存)。一般的,应用不会使用Wakelock,因为会很耗电并且也没必要,这意味着应用会不时的执行GC动作。通常情况下,GC动作会正常的执行完成而不会被挂起。但是,有些时候(很稀少),操作系统会在GC动作的过程中进入休眠。如果你的应用运行时间足够长,它就有可能发生。现在,想一下GC循环中的有关时间戳的逻辑:有可能发生,设备开始进行GC,并且在处理系统对象销毁(native层的destroy())的过程中进入休眠, 然后被唤醒,恢复运行,记录现在的时间戳,也就是说这次GC动作花费的时间=销毁动作执行时长+休眠时长。如果休眠时间超过10s, 就会抛出concurrent.timeout异常。
这个问题不能完全避免,只要你的应用在后台运行。我们可以通过调用wakelock减少设备休眠,但这个方法会引来一系列问题。最靠谱的方法还是,** 尽量减少GC动作的被调用次数 **,使得这个场景少出现。
另外,在Android5.0+系统上,因为使用了ART GC,使得这个崩溃的发生机率大大降低了。
目前Android project增加了大量的关于'GC是如何(在ART上)工作' 的之类的文档,感兴趣的话,可以看看:https://source.android.com/devices/tech/dalvik/gc-debug.html