GcWatcher.finalize, BinderProxy.finalize
とPlainSocketImpl.finalize
には多くのTimeoutExceptions
が表示されています。それらの90%以上がAndroid 4.3で発生します。現場のユーザーからCrittercismからこの報告を受けています。
エラーは「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)
これまでのところ、家で問題を再現したり、原因を突き止めたりする運がありませんでした。
これを引き起こす原因は何ですか?これをデバッグして、アプリのどの部分がこれを引き起こすかを知る方法はありますか?問題を明らかにするものは何でも役立ちます。
その他のスタックトレース:
1 Android.os.BinderProxy.destroy
2 Android.os.BinderProxy.finalize Binder.Java, line 482
3 Java.lang.Daemons$FinalizerDaemon.doFinalize Daemons.Java, line 187
4 Java.lang.Daemons$FinalizerDaemon.run Daemons.Java, line 170
5 Java.lang.Thread.run Thread.Java, line 841
2
1 Java.lang.Object.wait
2 Java.lang.Object.wait Object.Java, line 401
3 Java.lang.ref.ReferenceQueue.remove ReferenceQueue.Java, line 102
4 Java.lang.ref.ReferenceQueue.remove ReferenceQueue.Java, line 73
5 Java.lang.Daemons$FinalizerDaemon.run Daemons.Java, line 170
6 Java.lang.Thread.run
3
1 Java.util.HashMap.newKeyIterator HashMap.Java, line 907
2 Java.util.HashMap$KeySet.iterator HashMap.Java, line 913
3 Java.util.HashSet.iterator HashSet.Java, line 161
4 Java.util.concurrent.ThreadPoolExecutor.interruptIdleWorkers ThreadPoolExecutor.Java, line 755
5 Java.util.concurrent.ThreadPoolExecutor.interruptIdleWorkers ThreadPoolExecutor.Java, line 778
6 Java.util.concurrent.ThreadPoolExecutor.shutdown ThreadPoolExecutor.Java, line 1357
7 Java.util.concurrent.ThreadPoolExecutor.finalize ThreadPoolExecutor.Java, line 1443
8 Java.lang.Daemons$FinalizerDaemon.doFinalize Daemons.Java, line 187
9 Java.lang.Daemons$FinalizerDaemon.run Daemons.Java, line 170
10 Java.lang.Thread.run
4
1 com.Android.internal.os.BinderInternal$GcWatcher.finalize BinderInternal.Java, line 47
2 Java.lang.Daemons$FinalizerDaemon.doFinalize Daemons.Java, line 187
3 Java.lang.Daemons$FinalizerDaemon.run Daemons.Java, line 170
4 Java.lang.Thread.run
完全開示-私はTLV DroidConで前述した講演の著者です。
私は多くのAndroidアプリケーションでこの問題を調査し、それに遭遇した他の開発者と議論する機会がありました-そして、私たち全員が同じポイントに達しました:この問題は回避できず、最小限に抑えられます。
Androidガベージコレクターコードの既定の実装を詳しく調べて、この例外がスローされる理由と、考えられる原因について理解を深めました。実験中に根本的な原因を見つけました。
問題の根本は、デバイスがしばらく「スリープ状態になる」時点です-これは、OSがほとんどのユーザーランドプロセスをしばらく停止し、画面をオフにしてCPUサイクルを削減することにより、バッテリー消費を削減することを決定したことを意味しますなど。これが行われる方法は、プロセスが実行中に一時停止されるLinuxシステムレベルです。これは、通常のアプリケーション実行中にいつでも発生する可能性がありますが、コンテキストレベルの切り替えはカーネルレベルで行われるため、ネイティブシステムコールで停止します。だから-これはDalvik GCがストーリーに参加する場所です。 Dalvik GCコード(AOSPサイトのDalvikプロジェクトで実装)は、複雑なコードではありません。基本的な動作方法については、DroidConスライドで説明しています。コレクターがファイナライズ(および破棄)するオブジェクトのリストを持つポイントで、私がカバーしなかったのは基本的なGCループです。ベースのループロジックは次のように簡略化できます。
finalize()
必要に応じてネイティブdestroy()
を呼び出し、end_timestamp
を取る、end_timestamp - starting_timestamp
)を計算し、ハードコードされた10秒のタイムアウト値と比較します。concurrent.TimeoutException
をスローしてプロセスを強制終了します。アプリケーションはそれを実行します。これはユーザー向けアプリケーションではなく、バックグラウンドで実行されます。このバックグラウンド操作中に、オブジェクトが作成、使用され、メモリを解放するために収集する必要があります。アプリケーションはWakelockを気にしません-これはバッテリーに悪影響を及ぼし、不要なようです。これは、アプリケーションが随時GCを呼び出すことを意味します。通常、GCの実行は問題なく完了します。時々(ごくまれに)、GC実行の途中でシステムがスリープ状態になることがあります。これは、アプリケーションを十分に長く実行し、Dalvikメモリログを綿密に監視すると発生します。次に、基本的なGCループのタイムスタンプロジックを検討します。デバイスが実行を開始し、start_stampを取得し、システムオブジェクトのdestroy()
ネイティブコールでスリープ状態になる可能性があります。起動して実行を再開すると、destroy()
が終了し、次のend_stampはdestroy()
呼び出しにかかった時間+スリープ時間になります。スリープ時間が長い場合-10秒を超えると、concurrent.timeout例外がスローされます。
これは、分析したpythonスクリプト-Androidシステムアプリケーションから生成されたグラフで見ました。自分の監視対象アプリだけではありません。十分なログを収集すると、最終的にそれが表示されます。
この問題を回避することはできません-アプリがバックグラウンドで実行されている場合、問題が発生します。ウェイクロックを取得してデバイスのスリープ状態を防ぐことで軽減できますが、それはまったく別の話であり、新しい頭痛、そして別の詐欺の別の話かもしれません。
GC呼び出しを減らすことで問題を最小限に抑えることができます-シナリオの可能性を低くします。ヒントはスライドにあります。
Dalvik 2(別名ART)GCコードを調べる機会はまだありません。これは、新しい世代別圧縮機能を備えているか、Android Lollipopで実験を行いました。
2015年7月5日追加:
このクラッシュタイプのクラッシュレポートの集計を確認した後、Android OS(Lollipop with ART)のバージョン5.0以降からのこれらのクラッシュは、このクラッシュタイプの0.5%のみを占めているようです。これは、ART GCの変更によりこれらのクラッシュの頻度が減少したことを意味します。
2016年6月1日に追加:
Androidプロジェクトは、Dalvik 2.0(別名ART)でのGCの動作に関する多くの情報を追加したようです。これについてはここで読むことができます- デバッグガベージコレクション 。また、アプリのGCの動作に関する情報を取得するためのツールについても説明します。 SIGQUITをアプリプロセスに送信すると、基本的にANRが発生し、分析のためにアプリケーションの状態がログファイルにダンプされます。
Crashlyticsを使用して、アプリ全体でこれを常に確認しています。クラッシュは通常、プラットフォームコードのかなり下で発生します。小さなサンプリング:
Android.database.CursorWindow.finalize()は10秒後にタイムアウトしました
Java.util.regex.Matcher.finalize()が10秒後にタイムアウトになりました
Android.graphics.Bitmap $ BitmapFinalizer.finalize()は10秒後にタイムアウトしました
org.Apache.http.impl.conn.SingleClientConnManager.finalize()は10秒後にタイムアウトしました
Java.util.concurrent.ThreadPoolExecutor.finalize()が10秒後にタイムアウトになりました
Android.os.BinderProxy.finalize()が10秒後にタイムアウトしました
Android.graphics.Path.finalize()は10秒後にタイムアウトしました
これが発生するデバイスは、Samsung製の圧倒的(しかし排他的ではない)デバイスです。これは、ほとんどのユーザーがSamsungデバイスを使用していることを意味する場合があります。または、Samsungデバイスの問題を示している可能性があります。よくわからない。
これは実際にはあなたの質問に答えるものではないと思いますが、これは非常に一般的であり、アプリケーションに固有のものではないことを強調したいだけです。
この問題に関するスライドを見つけました。
このスライドでは、著者は、ヒープ内に多くのオブジェクトまたは巨大なオブジェクトがある場合、GCに問題があるようだと述べています。スライドには、サンプルアプリへの参照と、この問題を分析するpythonスクリプトも含まれています。
https://github.com/oba2cat3/GCTest
https://github.com/oba2cat3/logcat2memorygraph
さらに、こちらのコメント#3でヒントを見つけました: https://code.google.com/p/Android/issues/detail?id=53418#c
ブロードキャストレシーバーは10秒後にタイムアウトします。おそらく、ブロードキャストレシーバーから非同期呼び出し(間違った)を実行し、4.3がそれを実際に検出します。
FinalizerWatchdogDaemon
を停止することで問題を解決しました。
public static void fix() {
try {
Class clazz = Class.forName("Java.lang.Daemons$FinalizerWatchdogDaemon");
Method method = clazz.getSuperclass().getDeclaredMethod("stop");
method.setAccessible(true);
Field field = clazz.getDeclaredField("INSTANCE");
field.setAccessible(true);
method.invoke(field.get(null));
}
catch (Throwable e) {
e.printStackTrace();
}
}
attachBaseContext()
のように、アプリケーションのライフサイクルでメソッドを呼び出すことができます。同じ理由で、電話の製造元を特定して問題を解決することもできますが、それはあなた次第です。
常に正しいことの1つは、現時点では、デバイスがメモリを窒息させていることです(通常、GCがトリガーされる可能性が最も高い理由です)。
ほぼすべての著者が以前に述べたように、アプリがバックグラウンドにあるときにAndroidがGCを実行しようとすると、この問題が表面化します。これを確認したほとんどの場合、ユーザーは画面をロックしてアプリを一時停止しました。これは、アプリケーションのどこかでメモリリークが発生したか、デバイスが既にロードされすぎていることも示している可能性があります。したがって、それを最小化する唯一の正当な方法は次のとおりです。
try {
Class<?> c = Class.forName("Java.lang.Daemons");
Field maxField = c.getDeclaredField("MAX_FINALIZE_NANOS");
maxField.setAccessible(true);
maxField.set(null, Long.MAX_VALUE);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
この問題を解決するためのディディからの効果的なソリューションは次のとおりです、このバグは非常に一般的で原因を見つけるのが難しいため、システムの問題のように見えます、なぜそれを直接無視できないのですか?もちろん、ここでそれを無視できますサンプルコードです。
final Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler =
Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
if (t.getName().equals("FinalizerWatchdogDaemon") && e instanceof TimeoutException) {
} else {
defaultUncaughtExceptionHandler.uncaughtException(t, e);
}
}
});
特別なデフォルトのキャッチされない例外ハンドラを設定することにより、アプリケーションは、システムが提供するデフォルトの動作を既に受け入れるスレッドに対してキャッチされない例外の処理方法を変更できます。キャッチされていないTimeoutException
がFinalizerWatchdogDaemon
という名前のスレッドからスローされると、この特別なハンドラーはハンドラーチェーンをブロックし、システムハンドラーは呼び出されないため、クラッシュは回避されます。
練習を通して、他の悪影響は見つかりませんでした。 GCシステムはまだ動作しており、CPU使用率が減少するにつれてタイムアウトが緩和されます。
詳細については、以下を参照してください。 https://mp.weixin.qq.com/s/uFcFYO2GtWWiblotem2bGg
FinalizeQueueが長すぎる可能性があります
Javaには GC.SuppressFinalize() && GC.ReRegisterForFinalize() が必要で、ユーザーがfinalizedQueueの長さを明示的に減らすことができると思う
jVMのソースコードが利用可能な場合、Android ROM makerなど、これらのメソッドを自分で実装できます。
Androidランタイムバグのようです。個別のスレッドで実行され、スタックトレースの現在のフレームにないオブジェクトに対してfinalize()メソッドを呼び出すファイナライザーがあるようです。たとえば、次のコード(この問題を検証するために作成)はクラッシュで終了しました。
Finalizeメソッドで何かを行うカーソルを用意しましょう(例:SqlCipherのもの、現在使用中のデータベースにロックするclose()を実行します)
private static class MyCur extends MatrixCursor {
public MyCur(String[] columnNames) {
super(columnNames);
}
@Override
protected void finalize() {
super.finalize();
try {
for (int i = 0; i < 1000; i++)
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
そして、カーソルを開いて長時間実行します。
for (int i = 0; i < 7; i++) {
new Thread(new Runnable() {
@Override
public void run() {
MyCur cur = null;
try {
cur = new MyCur(new String[]{});
longRun();
} finally {
cur.close();
}
}
private void longRun() {
try {
for (int i = 0; i < 1000; i++)
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
これにより、次のエラーが発生します。
FATAL EXCEPTION: FinalizerWatchdogDaemon
Process: la.la.land, PID: 29206
Java.util.concurrent.TimeoutException: MyCur.finalize() timed out after 10 seconds
at Java.lang.Thread.sleep(Native Method)
at Java.lang.Thread.sleep(Thread.Java:371)
at Java.lang.Thread.sleep(Thread.Java:313)
at MyCur.finalize(MessageList.Java:1791)
at Java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.Java:222)
at Java.lang.Daemons$FinalizerDaemon.run(Daemons.Java:209)
at Java.lang.Thread.run(Thread.Java:762)
SqlCipherの製品版は非常に似ています:
12-21 15:40:31.668: E/EH(32131): Android.content.ContentResolver$CursorWrapperInner.finalize() timed out after 10 seconds
12-21 15:40:31.668: E/EH(32131): Java.util.concurrent.TimeoutException: Android.content.ContentResolver$CursorWrapperInner.finalize() timed out after 10 seconds
12-21 15:40:31.668: E/EH(32131): at Java.lang.Object.wait(Native Method)
12-21 15:40:31.668: E/EH(32131): at Java.lang.Thread.parkFor$(Thread.Java:2128)
12-21 15:40:31.668: E/EH(32131): at Sun.misc.Unsafe.park(Unsafe.Java:325)
12-21 15:40:31.668: E/EH(32131): at Java.util.concurrent.locks.LockSupport.park(LockSupport.Java:161)
12-21 15:40:31.668: E/EH(32131): at Java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.Java:840)
12-21 15:40:31.668: E/EH(32131): at Java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.Java:873)
12-21 15:40:31.668: E/EH(32131): at Java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.Java:1197)
12-21 15:40:31.668: E/EH(32131): at Java.util.concurrent.locks.ReentrantLock$FairSync.lock(ReentrantLock.Java:200)
12-21 15:40:31.668: E/EH(32131): at Java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.Java:262)
12-21 15:40:31.668: E/EH(32131): at net.sqlcipher.database.SQLiteDatabase.lock(SourceFile:518)
12-21 15:40:31.668: E/EH(32131): at net.sqlcipher.database.SQLiteProgram.close(SourceFile:294)
12-21 15:40:31.668: E/EH(32131): at net.sqlcipher.database.SQLiteQuery.close(SourceFile:136)
12-21 15:40:31.668: E/EH(32131): at net.sqlcipher.database.SQLiteCursor.close(SourceFile:510)
12-21 15:40:31.668: E/EH(32131): at Android.database.CursorWrapper.close(CursorWrapper.Java:50)
12-21 15:40:31.668: E/EH(32131): at Android.database.CursorWrapper.close(CursorWrapper.Java:50)
12-21 15:40:31.668: E/EH(32131): at Android.content.ContentResolver$CursorWrapperInner.close(ContentResolver.Java:2746)
12-21 15:40:31.668: E/EH(32131): at Android.content.ContentResolver$CursorWrapperInner.finalize(ContentResolver.Java:2757)
12-21 15:40:31.668: E/EH(32131): at Java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.Java:222)
12-21 15:40:31.668: E/EH(32131): at Java.lang.Daemons$FinalizerDaemon.run(Daemons.Java:209)
12-21 15:40:31.668: E/EH(32131): at Java.lang.Thread.run(Thread.Java:762)
再開:できるだけ早くカーソルを閉じます少なくとも、問題が発生したAndroid 7のSamsung S8で。
作成する(つまりAndroidの一部ではない)クラスの場合、クラッシュを完全に回避することが可能です。
finalize()
を実装するクラスには、@ obaで説明されているように、クラッシュの避けられない確率があります。したがって、ファイナライザを使用してクリーンアップを実行する代わりに、PhantomReferenceQueue
を使用します。
例については、React Nativeの実装を確認してください。 https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/Java/com/facebook /jni/DestructorThread.Java