有時會遇到以下崩潰:
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)
它其實是發(fā)生在 GcWatcher.finalize, BinderProxy.finalize 和 PlainSocketImpl.finalize 中的一類TimeoutExceptions。這個異常90%都是發(fā)生在4.3、4.4的android系統(tǒng)上。
這個問題的根源在于設備會'Goes to Sleep'一會兒,就是說操作系統(tǒng)會通過熄屏、降低cpu循環(huán)等方式降低電量消耗,進入休眠狀態(tài)。它是通過在內(nèi)核層暫停進程的方式來實現(xiàn)的。這可能發(fā)生在常規(guī)app運行的過程中, 但是會停在一次內(nèi)核調(diào)用上,比如內(nèi)核層的上下文切換。這就是Dalvik GC參與最初所說TimeoutExceptions問題的方式。
Dalvik GC的基本工作方式就是,在GC循環(huán)中將收集到的一系列的對象去銷毀,其主要流程可以描述為:
- 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.
現(xiàn)在思考下接下來的這個場景:
有一個后臺運行的進程,在運行過程中,對象被創(chuàng)建、使用以及被收集(以釋放內(nèi)存)。一般的,應用不會使用Wakelock,因為會很耗電并且也沒必要,這意味著應用會不時的執(zhí)行GC動作。通常情況下,GC動作會正常的執(zhí)行完成而不會被掛起。但是,有些時候(很稀少),操作系統(tǒng)會在GC動作的過程中進入休眠。如果你的應用運行時間足夠長,它就有可能發(fā)生。現(xiàn)在,想一下GC循環(huán)中的有關時間戳的邏輯:有可能發(fā)生,設備開始進行GC,并且在處理系統(tǒng)對象銷毀(native層的destroy())的過程中進入休眠, 然后被喚醒,恢復運行,記錄現(xiàn)在的時間戳,也就是說這次GC動作花費的時間=銷毀動作執(zhí)行時長+休眠時長。如果休眠時間超過10s, 就會拋出concurrent.timeout異常。
這個問題不能完全避免,只要你的應用在后臺運行。我們可以通過調(diào)用wakelock減少設備休眠,但這個方法會引來一系列問題。最靠譜的方法還是,** 盡量減少GC動作的被調(diào)用次數(shù) **,使得這個場景少出現(xiàn)。
另外,在Android5.0+系統(tǒng)上,因為使用了ART GC,使得這個崩潰的發(fā)生機率大大降低了。
目前Android project增加了大量的關于'GC是如何(在ART上)工作' 的之類的文檔,感興趣的話,可以看看:https://source.android.com/devices/tech/dalvik/gc-debug.html