在android開發中常常會到一些即便看了堆棧也沒法快速定位的問題,由於這些堆棧幾乎都是系統代碼,並沒有業務代碼,並且發生crash打印的堆棧也不必定是這個地方致使的。例如咱們今天要討論的java.util.concurrent.TimeoutException,咱們這裏能查詢到一個上報的堆棧以下:java
能夠看到這些都是系統的堆棧,咱們也沒法快速定位到業務中究竟是哪裏致使了這個crash,只能從給出的堆棧知道是在系統回收資源AssetManager進行析構時超時致使的異常。android
上網查詢後發現,這其實已經算是一個比較廣泛的問題,並且大多發生在OPPO和360手機中,究其緣由:安全
Android在啓動後會建立一些守護線程,其中涉及到該問題的有兩個,分別是FinalizerDaemon和FinalizerWatchdogDaemon.函數
對FinalizerDaemon析構守護線。對於重寫了成員函數finalize的對象,當它們被GC決定要被回收時,並不會立刻被回收,而是被放入到一個隊列中,等待FinalizerDaemon守護線程去調用它們的成員函數finalize後再被回收。this
FinalizerWatchdogDaemon析構監聽守護線程,用來監控FinalizerDaemon線程的執行。一旦監測到那些重寫了finalize的對象在執行成員函數finalize時超出必定時間,那麼就會退出VM。編碼
從上面的分析知道,若是FinalizerDaemon進行對象析構時超過了MAX_FINALIZE_NANOS(默認10s,各個Rom廠商極可能會更改這個參數。例如OPPO不少機器上這個參數被改爲了120s),FinalizerWatchdogDaemon進行就會拋出TimeoutExceptionspa
Daemons.java#FinalizerWatchdogDaemon線程
private static void finalizerTimedOut(Object object) {
// The current object has exceeded the finalization deadline; abort!
String message = object.getClass().getName() + ".finalize() timed out after "+ (MAX_FINALIZE_NANOS / NANOS_PER_SECOND) + " seconds";
Exception syntheticException = new TimeoutException(message);
……
}
複製代碼
10s的超時實際上是很大的一個值,通常的析構方法的執行時間很難超過這個數。咱們大體推斷髮生這種crash的特色:3d
從Stack Overflow上找到了一個相對比較合理的出現場景:code
可見應用後臺執行的時間越長,出現的機率應該就會越大。
咱們上面分析了發生這種TimeOut異常的緣由,知道要根治這個問題,仍是要合理的編碼,特別在涉及到內存分配方面時。那麼到底什麼纔是合理編碼,怎麼才能合理的申請的內存、複用內存和回收內存呢。這是一個仁者見仁智者見智的事情,也不是咱們本文討論的重點。這裏咱們提供一種折中的補救措施。就是在咱們的應用進程起來後,咱們經過反射主動關閉FinalizerWatchdogDaemon線程對析構過程的監聽,這樣即便FinalizerDaemon 調用對象的finalize進行析構回收超時了,也不會拋出這個TimeOut異常了。
private void stopWatchdogDaemon() {
GLog.i(TAG, "---stopWatchdogDaemon---");
try {
/** * 1.獲取Daemons$FinalizerWatchdogDaemon的單例實例INSTANCE */
Class clazz = Class.forName("java.lang.Daemons$FinalizerWatchdogDaemon");
Field field = clazz.getDeclaredField("INSTANCE");
field.setAccessible(true);
Object watchDog = field.get(null);
try {
/** * 2.將Daemon的成員變量thread設置爲null */
Field thread = clazz.getSuperclass().getDeclaredField("thread");
thread.setAccessible(true);
thread.set(watchDog, null);
} catch (Throwable throwable) {
GLog.e(TAG, "set thread null to stop watchDog error, throwable: " + throwable.getMessage());
try {
/** * 3.若是2中將thread置null失敗,則直接調用Daemon的stop方法 */
Method method = clazz.getSuperclass().getDeclaredMethod("stop");
method.setAccessible(true);
method.invoke(watchDog);
} catch (Throwable error) {
GLog.e(TAG, "invoke stop method to stop watchDog error, throwable: " + error.getMessage());
}
}
} catch (Throwable throwable) {
GLog.e(TAG, "get obj to stop watchDog error, throwable: " + throwable.getMessage());
}
}
複製代碼
上面經過反射首先將FinalizerWatchdogDaemon父類Daemon中的thread置空,若是失敗再經過反射調用FinalizerWatchdogDaemon父類Daemon的stop方法繼續將成員變量thread置空(以下代碼中的1處註釋所示),並中止線程(下代碼中的2處註釋所示)
SDK=28 Daemons.java#Daemon
/** * Waits for the runtime thread to stop. This interrupts the thread * currently running the runnable and then waits for it to exit. */
public void stop() {
Thread threadToStop;
synchronized (this) {
// 1.外部調用置空
threadToStop = thread;
thread = null;
}
if (threadToStop == null) {
throw new IllegalStateException("not running");
}
// 2.中止線程
interrupt(threadToStop);
while (true) {
try {
threadToStop.join();
return;
} catch (InterruptedException ignored) {
} catch (OutOfMemoryError ignored) {
// An OOME may be thrown if allocating the InterruptedException failed.
}
}
}
// 3.線程安全的操做
public synchronized void interrupt(Thread thread) {
if (thread == null) {
throw new IllegalStateException("not running");
}
thread.interrupt();
}
複製代碼
Ps:在6.0以前,當使用stop方法來中止線程時,是一個不安全的操做,可能會存在線程安全問題。以下代碼所示
參考