Come gestire: java.util.concurrent.TimeoutException: android.os.BinderProxy.finalize () scaduto dopo errori di 10 secondi?


167

Stiamo vedendo un numero di TimeoutExceptionsin GcWatcher.finalize, BinderProxy.finalizee PlainSocketImpl.finalize. Oltre il 90% di essi avviene su Android 4.3. Stiamo ricevendo segnalazioni di questo dal Crittercism dagli utenti sul campo.

inserisci qui la descrizione dell'immagine

L'errore è una variazione di: " 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)

Finora non abbiamo avuto fortuna a riprodurre il problema in casa o a capire cosa potesse averlo causato.

Qualche idea su cosa può causare questo? Qualche idea su come eseguire il debug e scoprire quale parte dell'app causa questo? Tutto ciò che fa luce sul problema aiuta.

Più 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

2
Non importa, l' ho trovato bugzilla.mozilla.org/show_bug.cgi?id=864102 Posso anche confermare che sta influenzando le nostre app, puzza come un problema di Google Play Services
eveliotc,

La riga di codice in cui viene generato l'errore è stata introdotta la versione 4.3_r1, che è stata rilasciata il 5 giugno 2013. Potrebbe essere il problema da allora in poi.
edubriguenti,

Anche la versione 4.2.2 di Android ha iniziato a lanciare questa eccezione, quindi forse è un aggiornamento di Google Play che è la fonte.
JWqvist,

@EvelioTarazona Ce l'ho in qualche app che non utilizza i servizi di gioco
ligi

@ligi è lo stesso stack-trace per te?
eveliotc,

Risposte:


220

Divulgazione completa - Sono l'autore del discorso precedentemente citato in TLV DroidCon.

Ho avuto la possibilità di esaminare questo problema su molte applicazioni Android e di discuterne con altri sviluppatori che lo hanno riscontrato - e siamo tutti arrivati ​​allo stesso punto: questo problema non può essere evitato, solo minimizzato.

Ho esaminato più da vicino l'implementazione predefinita del codice del raccoglitore Android Garbage, per capire meglio perché questa eccezione viene generata e su quali potrebbero essere le possibili cause. Ho anche trovato una possibile causa alla radice durante la sperimentazione.

La radice del problema è nel punto in cui un dispositivo "va in stop" per un po '- questo significa che il sistema operativo ha deciso di ridurre il consumo della batteria arrestando la maggior parte dei processi di Land utente per un po' e spegnendo lo schermo, riducendo i cicli della CPU , ecc. Il modo in cui ciò avviene è a livello di sistema Linux in cui i processi vengono messi in pausa a metà corsa. Ciò può accadere in qualsiasi momento durante la normale esecuzione dell'applicazione, ma si fermerà a una chiamata di sistema nativo, poiché il cambio di contesto viene eseguito a livello di kernel. Quindi - è qui che Dalvik GC si unisce alla storia.

Il codice GC Dalvik (implementato nel progetto Dalvik nel sito AOSP) non è un codice complicato. Il modo in cui funziona è illustrato nelle diapositive DroidCon. Quello che non ho trattato è il loop GC di base, nel punto in cui il collezionista ha un elenco di oggetti da finalizzare (e distruggere). La logica del loop alla base può essere semplificata in questo modo:

  1. prendere starting_timestamp,
  2. rimuovere l'oggetto per l'elenco degli oggetti da rilasciare,
  3. rilascia oggetto - finalize()e chiama nativo destroy()se necessario,
  4. prendere end_timestamp,
  5. calcola ( end_timestamp - starting_timestamp) e confronta con un valore di timeout codificato di 10 secondi,
  6. se è scaduto il timeout, lancia java.util.concurrent.TimeoutExceptione uccidi il processo.

Ora considera il seguente scenario:

L'applicazione continua a fare le sue cose.

Questa non è un'applicazione rivolta all'utente, ma viene eseguita in background.

Durante questa operazione in background, gli oggetti vengono creati, utilizzati e devono essere raccolti per liberare memoria.

L'applicazione non dà fastidio con un WakeLock, poiché ciò influirà negativamente sulla batteria e sembra non necessario.

Ciò significa che l'Applicazione invocherà periodicamente il GC.

Normalmente le corse GC sono completate senza intoppi.

A volte (molto raramente) il sistema deciderà di dormire durante la corsa GC.

Ciò accadrà se esegui l'applicazione abbastanza a lungo e monitori attentamente i log di memoria di Dalvik.

Ora - considera la logica del timestamp del loop GC di base - è possibile per il dispositivo avviare la corsa, prendere un start_stampe andare a dormire alla destroy()chiamata nativa su un oggetto di sistema.

Quando si sveglia e riprende la corsa, destroy()finirà, e il prossimo end_stampsarà il tempo impiegato dalla destroy()chiamata + il tempo di spegnimento.

Se il tempo di sonno è stato lungo (più di 10 secondi), java.util.concurrent.TimeoutExceptionverrà lanciato.

L'ho visto nei grafici generati dallo script di analisi Python - per le applicazioni di sistema Android, non solo le mie app monitorate.

Raccogli abbastanza registri e alla fine lo vedrai.

Linea di fondo:

Il problema non può essere evitato: lo riscontrerai se la tua app viene eseguita in background.

Puoi mitigare prendendo un WakeLock e impedire al dispositivo di dormire, ma questa è una storia completamente diversa, un nuovo mal di testa e forse un altro discorso in un altro imbroglio.

È possibile ridurre al minimo il problema riducendo le chiamate GC - rendendo meno probabile lo scenario (i suggerimenti sono nelle diapositive).

Non ho ancora avuto la possibilità di ripassare il codice GC Dalvik 2 (aka ART), che vanta una nuova funzionalità di compattazione generazionale, o di eseguire alcun esperimento su un Lollipop Android.

5/5/2015 aggiunto:

Dopo aver esaminato l'aggregazione dei rapporti sugli arresti anomali per questo tipo di arresti anomali, sembra che questi arresti anomali dalla versione 5.0+ del sistema operativo Android (Lollipop con ART) rappresentino solo lo 0,5% di questo tipo di arresti anomali. Ciò significa che le modifiche di ART GC hanno ridotto la frequenza di questi arresti anomali.

Aggiunto il 6/1/2016:

Sembra che il progetto Android abbia aggiunto molte informazioni su come funziona il GC in Dalvik 2.0 (aka ART).

Puoi leggerlo qui - Debugging di ART Garbage Collection .

Descrive anche alcuni strumenti per ottenere informazioni sul comportamento GC per la tua app.

L'invio di un SIGQUIT al processo della tua app causerà essenzialmente un ANR e scaricherà lo stato dell'applicazione in un file di registro per l'analisi.


Nel mio caso, sto anche pianificando di provare a mitigarlo trovando modi per ridurre la quantità di codice / tempo in esecuzione in background. Grazie per la tua ricerca sull'argomento.
Parkerfath,

la rimozione di qualsiasi elaborazione in background eseguita nella tua app contribuirà notevolmente a ridurre il problema.
oba

Per quello che vale, questo accade ancora in Marshmallow (6.0.1). Detto questo, ho ricevuto questo errore solo una volta. Quindi non sembra essere un problema gigantesco. Grazie per la tua completa spiegazione.
Cnosso,

Dopo qualche tempo, ho avuto la netta impressione che risolvere questo problema nel sistema operativo fosse molto problematico e richiedesse la cooperazione tra Google e gli OEM. Non mi aspetto che questo problema venga risolto presto.
oba,

Sto usando wakelock ma ho ancora riscontrato questo problema su Android 4.4.2. La mia app ha alcune operazioni in background ma principalmente progettata per funzionare tutto il giorno mentre il cavo di ricarica è montato. Esiste un modo diverso per mitigare questo problema?
Orcun Sevsay,

74

Lo vediamo costantemente, in tutta la nostra app, usando Crashlytics. L'incidente di solito si verifica in fondo al codice della piattaforma. Un piccolo campionamento:

android.database.CursorWindow.finalize () scaduto dopo 10 secondi

java.util.regex.Matcher.finalize () scaduto dopo 10 secondi

android.graphics.Bitmap $ BitmapFinalizer.finalize () scaduto dopo 10 secondi

org.apache.http.impl.conn.SingleClientConnManager.finalize () scaduto dopo 10 secondi

java.util.concurrent.ThreadPoolExecutor.finalize () scaduto dopo 10 secondi

android.os.BinderProxy.finalize () scaduto dopo 10 secondi

android.graphics.Path.finalize () scaduto dopo 10 secondi

I dispositivi su cui ciò accade sono in modo schiacciante (ma non esclusivamente) prodotti da Samsung. Ciò potrebbe significare solo che la maggior parte dei nostri utenti utilizza dispositivi Samsung; in alternativa, potrebbe indicare un problema con i dispositivi Samsung. Non sono veramente sicuro.

Suppongo che questo non risponda davvero alle tue domande, ma volevo solo rinforzare che questo sembra abbastanza comune e non specifico per la tua applicazione.


16
Sta accadendo anche per la versione Android 5.0.1 e non sembra essere limitato ai dispositivi Samsung. Accadde su Nexus 6.
Shobhit Puri il

4
Ho questo problema su Android 4.4.4 con dispositivo prodotto da XIAOMI
Paresh Dudhat il

Volevo solo rinviare il fatto che stiamo assistendo alla maggior parte di questi arresti anomali sui tablet Samsung. Non sono sicuro di cosa Samsung abbia fatto diversamente con il modo in cui i tablet gestiscono le app in background.
FriendlyMikhail,

1
ho questo problema su Android 4.4.4. dispositivo prodotto da HUAWEI.
Rameshbabu,

1
La mia app si arresta in modo anomalo se utilizzo la libreria di gestione delle perdite sul dispositivo Android 5.0.2 Samsung. Se disabilito l'inizializzazione della libreria, l'app funziona perfettamente.
Vanomart,

15

Ho trovato alcune diapositive su questo problema.

http://de.slideshare.net/DroidConTLV/android-crash-analysis-and-the-dalvik-garbage-collector-tools-and-tips

In queste diapositive l'autore dice che sembra essere un problema con GC, se ci sono molti oggetti o enormi oggetti nell'heap. La diapositiva include anche un riferimento a un'app di esempio e uno script Python per analizzare questo problema.

https://github.com/oba2cat3/GCTest

https://github.com/oba2cat3/logcat2memorygraph

Inoltre ho trovato un suggerimento nel commento n. 3 su questo lato: https://code.google.com/p/android/issues/detail?id=53418#c3


7

Abbiamo risolto il problema bloccando il 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();
    }
}

È possibile chiamare il metodo nel ciclo di vita dell'applicazione, ad esempio attachBaseContext(). Per lo stesso motivo, puoi anche specificare la fabbricazione del telefono per risolvere il problema, dipende da te.


Non funziona per noi, non riesco a capire perché. Il codice viene completato senza eccezioni, ma riceviamo ancora tali problemi nei rapporti Crashlytics e Google Play Console.
Anton Breusov,

5

Timeout dei ricevitori di trasmissione dopo 10 secondi. Forse stai facendo una chiamata asincrona (errata) da un ricevitore broadcast e 4.3 la rileva effettivamente.


3
Sembra inutile rilevarlo e non parlarne abbastanza. Facci sapere quale trasmissione sarebbe bella.
Aaron T Harris,

Scusatemi se sbaglio, ma non credo che il timeout del ricevitore di trasmissione causi questo arresto anomalo. È buona norma evitare il limite dei 10, ma si tratta di un problema diverso rispetto al richiedente.
Parkerfath,

Ho solo 10 secondi nel cervello. developer.android.com/training/articles/perf-anr.html IDK se causava l'arresto anomalo.
danny117,

Il tuo punto è solido e una buona pratica. Tuttavia, il poster originale ha una domanda specifica su un set specifico di dispositivi. Consiglio agli altri spettatori di questo post di controllare la risposta di Christopher e la risposta di oba se stanno riscontrando gli stessi sintomi (dispositivi Samsung (in particolare Galaxy S 4), ecc.)
Parkerfath

Non sono qui per colpire i produttori di dispositivi sarebbe contro i termini.
danny117,

5

Ecco una soluzione efficace da parte di didi per risolvere questo problema, poiché questo errore è molto comune e difficile da trovare la causa, sembra più un problema di sistema, perché non possiamo ignorarlo direttamente? Naturalmente possiamo ignorarlo, qui è il codice di esempio:

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);
        }
    }
});

Impostando uno speciale gestore eccezioni non rilevate predefinito, l'applicazione può cambiare il modo in cui vengono gestite le eccezioni non rilevate per quei thread che accetterebbero già qualsiasi comportamento predefinito fornito dal sistema. Quando un non catturato TimeoutExceptionviene lanciato da un thread denominatoFinalizerWatchdogDaemon , questo gestore speciale bloccherà la catena di gestori, il gestore di sistema non verrà chiamato, quindi si eviterà l'arresto anomalo.

Attraverso la pratica, non sono stati trovati altri effetti negativi. Il sistema GC funziona ancora, i timeout sono ridotti quando diminuisce l'utilizzo della CPU.

Per maggiori dettagli, consultare: https://mp.weixin.qq.com/s/uFcFYO2GtWWiblotem2bGg


4

Una cosa che è invariabilmente vera è che in questo momento, il dispositivo sarebbe soffocante per un po 'di memoria (che di solito è il motivo per cui GC probabilmente viene attivato).

Come accennato in precedenza da quasi tutti gli autori, questo problema emerge quando Android tenta di eseguire GC mentre l'app è in background. Nella maggior parte dei casi in cui l'abbiamo osservata, l'utente ha messo in pausa l'app bloccando lo schermo. Ciò potrebbe anche indicare una perdita di memoria da qualche parte nell'applicazione o il dispositivo è già troppo carico. Quindi l'unico modo legittimo per minimizzarlo è:

  • per garantire che non vi siano perdite di memoria e
  • per ridurre il footprint di memoria dell'app in generale.

1
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();
}

Ciò non risolverà il problema nel caso in cui la durata del sonno sia> 100 secondi. Perché non impostarlo su MAX_INT?
oba,

Sì, sono solo un esempio ~
kot32

1
Questo non dovrebbe funzionare a causa del costante allineamento. La modifica del valore del campo non influirà sul valore inserito nei chiamanti.
hqzxzwb,

0

FinalizeQueue potrebbe essere troppo lungo

penso che Java possa richiedere GC.SuppressFinalize () e GC.ReRegisterForFinalize () per consentire all'utente di ridurre esplicitamente la lunghezza

se il codice sorgente della JVM è disponibile, è possibile implementare noi stessi questo metodo, come il produttore di ROM Android


0

Sembra un bug di runtime Android. Sembra esserci un finalizzatore che viene eseguito nel suo thread separato e chiama il metodo finalize () sugli oggetti se non si trovano nel frame corrente dello stacktrace. Ad esempio il seguente codice (creato per verificare questo problema) è terminato con l'arresto anomalo.

Prendiamo qualche cursore che fa qualcosa nel metodo finalize (es. Quelli SqlCipher, do close () che si blocca sul database attualmente in uso)

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();
        }
    }
}

E facciamo alcune cose di lunga durata dopo aver aperto il cursore:

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();
    }

Ciò provoca il seguente errore:

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 di produzione con SqlCipher è molto simile:

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)

Riprendi: chiudi i cursori APPENA POSSIBILE. Almeno su Samsung S8 con Android 7 in cui è stato riscontrato il problema.


0

Per le classi che crei (cioè non fanno parte di Android) è possibile evitare completamente il crash.

Ogni classe che implementa finalize()ha una inevitabile probabilità di crash, come spiegato da @oba. Quindi, invece di utilizzare i finalizzatori per eseguire la pulizia, utilizzare aPhantomReferenceQueue .

Per un esempio, controlla l'implementazione in React Native: https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/jni/DestructorThread.java

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.