Nous voyons un nombre de TimeoutExceptions
dans GcWatcher.finalize, BinderProxy.finalize
et PlainSocketImpl.finalize
. 90 +% d'entre eux se produisent sur Android 4.3. Nous recevons des informations de Crittercism à ce sujet de la part d’utilisateurs sur le terrain.
L'erreur est une variation de: "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)
Jusqu'ici, nous n'avons pas eu la chance de reproduire le problème en interne ou de déterminer ce qui l'aurait causé.
Avez-vous une idée de ce qui peut causer cela? Tout ce qui éclaire la question aide.
Plus de Stacktraces:
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
Divulgation complète - Je suis l'auteur de l'exposé mentionné précédemment dans TLV DroidCon.
J'ai eu l'occasion d'examiner ce problème dans de nombreuses applications Android et d'en discuter avec les autres développeurs qui l'ont rencontré - et nous sommes tous arrivés au même point: ce problème ne peut être évité, mais seulement minimisé.
J'ai examiné de plus près l'implémentation par défaut du code du collecteur Android Garbage afin de mieux comprendre pourquoi cette exception est Thrown et quelles pourraient en être les causes possibles. J'ai même trouvé une cause possible lors de l'expérimentation.
La racine du problème se situe au moment où un périphérique «se met en veille» pendant un certain temps - cela signifie que le système d'exploitation a décidé de réduire la consommation de la batterie en arrêtant la plupart des processus User Land pendant un certain temps et en désactivant Screen, réduisant ainsi les cycles du processeur. , etc. La façon dont cela est fait est au niveau du système Linux où les processus sont en pause à mi-parcours. Cela peut se produire à tout moment au cours de l'exécution normale de l'application, mais cela s'arrêtera lors d'un appel système natif, car la commutation de contexte est effectuée au niveau du noyau. Donc, c’est là que le GC Dalvik rejoint l’histoire . Le code du GC Dalvik (tel qu’implémenté dans le projet Dalvik sur le site AOSP) n’est pas un morceau de code compliqué. La manière dont cela fonctionne est décrite dans mes diapositives DroidCon. Ce que je n’ai pas abordé, c’est la boucle GC de base - au point où le collecteur a une liste d’objets à finaliser (et à détruire). la logique de boucle à la base peut être simplifiée comme ceci:
finalize()
et appeler destroy()
si nécessaire,end_timestamp
,end_timestamp - starting_timestamp
) et comparer avec une valeur de délai d'attente codée en dur de 10 secondes,concurrent.TimeoutException
et tuez le processus.L'application fonctionne le long de sa tâche. ce n'est pas une application orientée utilisateur, elle s'exécute en arrière-plan. Au cours de cette opération en arrière-plan, les objets sont créés, utilisés et doivent être collectés pour libérer de la mémoire. L'application ne s'embarrasse pas avec un Wakelock, car cela affecterait négativement la batterie et semblerait inutile. cela signifie que l'application invoquera le GC de temps à autre. Normalement, le cycle du GC est terminé sans accroc. Parfois (très rarement) le système décidera de dormir au milieu de la course du GC. Cela se produira si vous exécutez votre application suffisamment longtemps et surveillez de près les journaux de la mémoire Dalvik. Maintenant - tenez compte de la logique d'horodatage de la boucle de base du CPG - il est possible pour le périphérique de démarrer l'exécution, de procéder à un start_stamp et de passer en veille à l'appel natif destroy()
sur un objet système. quand il se réveillera et reprendra son exécution, la destroy()
se terminera et le prochain end_stamp sera l'heure à laquelle l'appel destroy()
a été pris + l'heure de veille. Si le temps de sommeil était long - plus de 10 secondes, l'exception concurrent.timeout sera levée.
Je l'ai constaté dans les graphiques générés à partir du script d'analyse Python - pour les applications système Android, et pas seulement pour mes propres applications surveillées. collecter suffisamment de journaux, vous finirez par le voir.
Le problème ne peut être évité - vous le rencontrerez si votre application s'exécute en arrière-plan. Vous pouvez atténuer l'effet en prenant un wakelock et empêcher le périphérique de dormir, mais c'est une tout autre histoire, un nouveau mal de tête et peut-être une autre conversation.
Vous pouvez minimiser le problème en réduisant les appels du GC - ce qui rend le scénario moins probable. les conseils sont dans les diapositives.
Je n'ai pas encore eu l'occasion de passer en revue le code GC Dalvik 2 (a.k.a ART), doté d'une nouvelle fonctionnalité de compactage générationnel, ni de réaliser des expériences sur un Lollipop Android.
Ajouté le 05/5/2015:
Après avoir examiné l'agrégation des rapports d'incident pour ce type d'incident, il semble que ces accidents de la version 5.0+ d'Android OS (Lollipop avec ART) ne représentent que 0,5% de ce type d'incident. Cela signifie que les modifications de l’ART GC ont réduit la fréquence de ces accidents.
Ajouté le 01/06/2016:
On dirait que le projet Android a ajouté de nombreuses informations sur le fonctionnement du GC dans Dalvik 2.0 (a.k.a ART). Vous pouvez en savoir plus à ce sujet ici - Débogage de la récupération de place ART . Il aborde également certains outils permettant d’obtenir des informations sur le comportement du CPG pour votre application. L'envoi d'un fichier SIGQUIT au processus de votre application provoquera essentiellement un ANR et dumpera l'état de l'application dans un fichier journal pour analyse.
Nous voyons cela constamment, partout dans notre application, en utilisant Crashlytics. Le crash se produit généralement bien en code de plateforme. Un petit échantillon:
Android.database.CursorWindow.finalize () a expiré après 10 secondes
Java.util.regex.Matcher.finalize () a expiré après 10 secondes
Android.graphics.Bitmap $ BitmapFinalizer.finalize () a expiré après 10 secondes
org.Apache.http.impl.conn.SingleClientConnManager.finalize () a expiré après 10 secondes
Java.util.concurrent.ThreadPoolExecutor.finalize () a expiré après 10 secondes
Android.os.BinderProxy.finalize () a expiré après 10 secondes
Android.graphics.Path.finalize () a expiré après 10 secondes
Les appareils sur lesquels cela se produit sont pour la plupart (mais pas exclusivement) des appareils fabriqués par Samsung. Cela pourrait simplement signifier que la plupart de nos utilisateurs utilisent des appareils Samsung; sinon, cela pourrait indiquer un problème avec les appareils Samsung. Je ne suis pas vraiment sûr.
Je suppose que cela ne répond pas vraiment à vos questions, mais je voulais simplement souligner que cela semble assez courant et n'est pas spécifique à votre application.
J'ai trouvé des diapositives sur ce problème.
Dans ces diapositives, l'auteur explique qu'il semble y avoir un problème avec GC, s'il y a beaucoup d'objets ou d'énormes objets dans heap. La diapositive comprend également une référence à un exemple d'application et un script python pour analyser ce problème.
https://github.com/oba2cat3/GCTest
https://github.com/oba2cat3/logcat2memorygraph
De plus, j'ai trouvé un indice dans le commentaire n ° 3 de ce côté: https://code.google.com/p/Android/issues/detail?id=53418#c3
Les récepteurs de diffusion expirent après 10 secondes. Vous effectuez éventuellement un appel asynchrone (incorrect) à partir d'un récepteur de diffusion et le 4.3 le détecte réellement.
Nous avons résolu le problème en arrêtant la 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();
}
}
Vous pouvez appeler la méthode dans le cycle de vie de l'application, comme attachBaseContext()
. Pour la même raison, vous pouvez également spécifier la fabrication du téléphone pour résoudre le problème, à vous de décider.
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();
}
Une chose qui est invariablement vraie est qu’à ce moment-là, le périphérique suffoquerait pour avoir un peu de mémoire (ce qui est généralement la raison pour laquelle GC est le plus susceptible de se déclencher).
Comme mentionné par presque tous les auteurs plus tôt, ce problème se pose lorsque Android tente d'exécuter GC lorsque l'application est en arrière-plan. Dans la plupart des cas où nous l'avons observé, l'utilisateur a suspendu l'application en verrouillant son écran . Cela pourrait également indiquer une fuite de mémoire quelque part dans l'application, ou le périphérique déjà trop chargé . minimiser c'est:
La file d'attente finalize peut être trop longue
je pense que Java peut nécessiter GC.SuppressFinalize () & GC.ReRegisterForFinalize () pour permettre à l'utilisateur de réduire explicitement la longueur finalizedQueue
si le code source de la machine virtuelle Java est disponible, vous pouvez implémenter vous-même cette méthode, telle qu'Android ROM maker
Cela ressemble à un bug d'Android Runtime. Il semble qu'un finaliseur s'exécute dans son thread séparé et appelle la méthode finalize () sur des objets s'ils ne figurent pas dans l'image actuelle de stacktrace .
Ayons un curseur qui fait quelque chose dans la méthode finalize (par exemple, ceux de SqlCipher, ferme () qui se verrouille sur la base de données en cours d'utilisation)
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();
}
}
}
Et nous faisons des trucs longs avec le curseur ouvert:
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();
}
Cela provoque l'erreur suivante:
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)
La variante de production avec SqlCipher est très similaire:
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)
Résumé: Fermer les curseurs dès que possible. Au moins sur Samsung S8 avec Android 7 où le problème a été vu.
Voici une solution efficace de didi pour résoudre ce problème, puisque ce bogue est très courant et qu'il est difficile d'en trouver la cause, il ressemble plus à un problème système, pourquoi ne pouvons-nous pas l'ignorer directement? Bien sûr, nous pouvons l'ignorer, ici est l'exemple de code:
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);
}
}
});
En définissant un gestionnaire d'exceptions par défaut non capturé spécial, l'application peut modifier la façon dont les exceptions non capturées sont gérées pour les threads qui accepteraient déjà le comportement par défaut fourni par le système. Quand une TimeoutException
non capturée est lancée à partir d'un thread nommé FinalizerWatchdogDaemon
, ce gestionnaire spécial bloquera la chaîne de gestionnaires, le gestionnaire de système ne sera pas appelé et un crash sera évité.
Grâce à la pratique, aucun autre effet négatif n'a été trouvé. Le système CPG fonctionne toujours, les délais d'attente sont réduits à mesure que l'utilisation du processeur diminue.
Pour plus de détails, voir: https://mp.weixin.qq.com/s/uFcFYO2GtWWiblotem2bGg
Pour les classes que vous créez (c.-à-d. Ne faisant pas partie d'Android), il est possible d'éviter complètement le crash.
Toute classe qui implémente finalize()
a une probabilité inévitable de planter, comme expliqué par @oba. Ainsi, au lieu d’utiliser des finaliseurs pour effectuer le nettoyage, utilisez un PhantomReferenceQueue
.
Pour un exemple, consultez la mise en œuvre dans React Native: https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/Java/com/facebook/jni/DestructorThread.Java