Como lidar com: java.util.concurrent.TimeoutException: android.os.BinderProxy.finalize () atingiu o tempo limite após erros de 10 segundos?


167

Estamos vendo um número de TimeoutExceptionsin GcWatcher.finalize, BinderProxy.finalizee PlainSocketImpl.finalize. Mais de 90% deles acontecem no Android 4.3. Estamos recebendo relatórios sobre isso do Crittercism de usuários em campo.

insira a descrição da imagem aqui

O erro é uma variação 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)

Até agora, não tivemos sorte em reproduzir o problema internamente ou em descobrir o que poderia ter causado o problema.

Alguma idéia do que pode causar isso? Alguma idéia de como depurar isso e descobrir qual parte do aplicativo causa isso? Tudo o que lança luz sobre o assunto ajuda.

Mais 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
Não importa, verificou- bugzilla.mozilla.org/show_bug.cgi?id=864102 posso também confirmar está afetando nossos aplicativos, cheira como uma questão do Google Play Services
eveliotc

A linha de código em que o erro foi lançado foi introduzida na versão 4.3_r1, lançada em 5 de junho de 2013. Pode ser que o problema esteja acontecendo desde então.
edubriguenti

A versão 4.2.2 do Android também começou a lançar essa exceção, então talvez seja uma atualização do Google Play que seja a fonte.
precisa

@EvelioTarazona eu tê-lo em algum aplicativo que não usa play-serviços
Ligi

@ligi é o mesmo rastreamento de pilha para você?
eveliotc

Respostas:


220

Divulgação completa - Sou o autor da palestra mencionada anteriormente no TLV DroidCon.

Tive a chance de examinar esse problema em muitos aplicativos Android e discuti-lo com outros desenvolvedores que o encontraram - e todos chegamos ao mesmo ponto: esse problema não pode ser evitado, apenas minimizado.

Analisei mais de perto a implementação padrão do código do coletor de lixo do Android, para entender melhor por que essa exceção é lançada e quais poderiam ser as possíveis causas. Eu até encontrei uma possível causa raiz durante a experimentação.

A raiz do problema está no momento em que um dispositivo "entra no modo de suspensão" por um tempo - isso significa que o sistema operacional decidiu diminuir o consumo de bateria, interrompendo a maioria dos processos de User Land por um tempo e desligando a tela, reduzindo os ciclos da CPU , etc. A maneira como isso é feito - é no nível do sistema Linux, onde os processos são Pausados ​​no meio da execução. Isso pode acontecer a qualquer momento durante a execução normal do aplicativo, mas será interrompido em uma chamada do sistema nativo, pois a alternância de contexto é feita no nível do kernel. Então - é aqui que o Dalvik GC se junta à história.

O código do Dalvik GC (conforme implementado no projeto Dalvik no site da AOSP) não é um trecho complicado de código. A maneira básica de como funciona é abordada nos meus slides do DroidCon. O que eu não cobri é o loop básico do GC - no ponto em que o coletor tem uma lista de objetos para finalizar (e destruir). A lógica do loop na base pode ser simplificada assim:

  1. tomar starting_timestamp,
  2. remover objeto para a lista de objetos a serem liberados,
  3. liberar objeto - finalize()e chamar nativo, destroy()se necessário,
  4. tomar end_timestamp,
  5. calcule ( end_timestamp - starting_timestamp) e compare com um valor de tempo limite codificado de 10 segundos,
  6. se o tempo limite chegar - jogue java.util.concurrent.TimeoutExceptione mate o processo.

Agora considere o seguinte cenário:

O aplicativo é executado ao mesmo tempo.

Este não é um aplicativo voltado para o usuário, é executado em segundo plano.

Durante essa operação em segundo plano, os objetos são criados, usados ​​e precisam ser coletados para liberar memória.

O aplicativo não se incomoda com um WakeLock - pois isso afetará adversamente a bateria e parece desnecessário.

Isso significa que o Aplicativo invocará o GC de tempos em tempos.

Normalmente, a execução do GC é concluída sem problemas.

Às vezes (muito raramente) o sistema decide dormir no meio da execução do GC.

Isso acontecerá se você executar o aplicativo por tempo suficiente e monitorar os logs de memória Dalvik de perto.

Agora - considere a lógica do registro de data e hora do loop básico do GC - é possível que o dispositivo inicie a execução, faça uma start_stampe vá dormir na destroy()chamada nativa em um objeto do sistema.

Quando ele acorda e retoma a execução, a destroy()finalização será concluída e a próxima end_stampserá a hora em que a destroy()ligação foi realizada + o tempo de suspensão.

Se o tempo de sono for longo (mais de 10 segundos), java.util.concurrent.TimeoutExceptionserá lançado.

Vi isso nos gráficos gerados a partir do script python de análise - para aplicativos do sistema Android, não apenas para meus próprios aplicativos monitorados.

Colete logs suficientes e você o verá eventualmente.

Bottom line:

O problema não pode ser evitado - você o encontrará se o aplicativo for executado em segundo plano.

Você pode atenuar usando um WakeLock e impedir que o dispositivo durma, mas isso é uma história completamente diferente, e uma nova dor de cabeça e talvez outra conversa em outro golpe.

Você pode minimizar o problema reduzindo as chamadas do GC - tornando o cenário menos provável (as dicas estão nos slides).

Ainda não tive a chance de revisar o código do Dalvik 2 (também conhecido como ART) GC - que possui um novo recurso de Compactação Geracional ou realizei experimentos em um pirulito Android.

Adicionado 7/5/2015:

Após revisar a agregação de relatórios de falhas para esse tipo de falha, parece que essas falhas da versão 5.0 ou superior do Android OS (Lollipop with ART) representam apenas 0,5% desse tipo de falha. Isso significa que as alterações do ART GC reduziram a frequência dessas falhas.

Adicionado 1/6/2016:

Parece que o projeto Android adicionou muitas informações sobre como o GC funciona no Dalvik 2.0 (também conhecido como ART).

Você pode ler sobre isso aqui - Depurando ART Garbage Collection .

Ele também discute algumas ferramentas para obter informações sobre o comportamento do GC para seu aplicativo.

Enviar um SIGQUIT para o processo do aplicativo causará essencialmente um ANR e despejará o estado do aplicativo em um arquivo de log para análise.


No meu caso, também estou planejando tentar mitigar isso, encontrando maneiras de reduzir a quantidade de código / tempo que estou executando em segundo plano. Obrigado pela sua pesquisa sobre o tema.
Parkerfath

remover qualquer processamento em segundo plano feito no seu aplicativo ajudará bastante a reduzir o problema.
oba

Pelo que vale, isso acontece ainda no Marshmallow (6.0.1). Dito isto, só recebi esse erro apenas uma vez. Portanto, não parece ser um problema gigantesco. Obrigado por sua explicação completa.
Knossos

Depois de algum tempo, tive a nítida impressão de que corrigir esse problema no sistema operacional é muito problemático e requer cooperação entre o Google e os OEMs. Não espero que isso seja corrigido tão cedo.
oba

Estou usando o wakelock, mas ainda encontrei esse problema no Android 4.4.2. Meu aplicativo tem algumas operações em segundo plano, mas foi projetado principalmente para funcionar o dia todo enquanto o cabo de carregamento é montado. Existe alguma maneira diferente de mitigar esse problema?
Orcun Sevsay

74

Vemos isso constantemente, em todo o nosso aplicativo, usando Crashlytics. A falha geralmente acontece no código da plataforma. Uma pequena amostra:

O tempo limite android.database.CursorWindow.finalize () atingiu o tempo limite após 10 segundos

O tempo limite de java.util.regex.Matcher.finalize () atingiu o tempo limite após 10 segundos

O tempo limite android.graphics.Bitmap $ BitmapFinalizer.finalize () atingiu o tempo limite após 10 segundos

O tempo limite de org.apache.http.impl.conn.SingleClientConnManager.finalize () atingiu o tempo limite após 10 segundos

O tempo limite de java.util.concurrent.ThreadPoolExecutor.finalize () atingiu o tempo limite após 10 segundos

O tempo limite android.os.BinderProxy.finalize () atingiu o tempo limite após 10 segundos

O tempo limite android.graphics.Path.finalize () atingiu o tempo limite após 10 segundos

Os dispositivos nos quais isso acontece são predominantemente (mas não exclusivamente) dispositivos fabricados pela Samsung. Isso pode significar apenas que a maioria dos nossos usuários está usando dispositivos Samsung; alternativamente, isso pode indicar um problema nos dispositivos Samsung. Eu não tenho certeza.

Suponho que isso realmente não responda às suas perguntas, mas eu só queria reforçar que isso parece bastante comum e não é específico para o seu aplicativo.


16
Está acontecendo também para a versão Android 5.0.1 e não parece estar restrita a dispositivos Samsung. Aconteceu no Nexus 6.
Shobhit Puri

4
Eu tenho esse problema no Android 4.4.4 com dispositivo fabricado por XIAOMI
Paresh Dudhat

Só queria gritar que estamos vendo a maioria dessas falhas em tablets samsung. Não sei o que a Samsung fez de diferente com a forma como os tablets lidam com aplicativos em segundo plano.
FriendlyMikhail

1
Eu tenho esse problema no android 4.4.4. dispositivo fabricado pela HUAWEI.
Rameshbabu

1
Meu aplicativo falha depois se eu usar a biblioteca de canários de vazamento no dispositivo Samsung 5.0.2 android. Se eu desativar a inicialização da biblioteca, o aplicativo funcionará perfeitamente.
Vanomart # 6/16

15

Encontrei alguns slides sobre esse problema.

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

Nestes slides, o autor diz que parece haver um problema com o GC, se houver muitos objetos ou objetos enormes no heap. O slide também inclui uma referência a um aplicativo de exemplo e um script python para analisar esse problema.

https://github.com/oba2cat3/GCTest

https://github.com/oba2cat3/logcat2memorygraph

Além disso, encontrei uma dica no comentário nº 3 deste lado: https://code.google.com/p/android/issues/detail?id=53418#c3


7

Resolvemos o problema parando o 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();
    }
}

Você pode chamar o método no ciclo de vida do aplicativo, como attachBaseContext(). Pelo mesmo motivo, você também pode especificar a fabricação do telefone para resolver o problema, a decisão é sua.


Não funciona para nós, não consigo entender o porquê. O código é concluído sem exceções, mas ainda recebemos esses problemas nos relatórios do Crashlytics e no Google Play Console.
Anton Breusov

5

Tempo limite dos receptores de transmissão após 10 segundos. Possivelmente você está fazendo uma chamada assíncrona (incorreta) de um receptor de broadcast e o 4.3 realmente a detecta.


3
Parece inútil detectá-lo e não lhe dizer o suficiente. Deixando-nos saber qual transmissão seria legal.
Aaron T Harris

Perdoe se eu estiver errado, mas não acho que o tempo limite do receptor de transmissão cause esse travamento específico. É uma boa prática evitar o limite de 10s, mas esse é um problema diferente do que o solicitante está enfrentando.
Parkerfath 8/12

Eu só tenho 10 segundos no cérebro. developer.android.com/training/articles/perf-anr.html IDK se estiver causando a falha também.
precisa saber é o seguinte

Seu ponto de vista é sólido e é uma boa prática. No entanto, o pôster original tem uma pergunta específica sobre um conjunto específico de dispositivos. Eu aconselho outros espectadores deste post para verificar a resposta de Christopher ea resposta de oba se eles estão vendo os mesmos sintomas (dispositivos Samsung (esp Galaxy S 4), etc..)
parkerfath

Eu não estou aqui para bash fabricantes de dispositivos, seria contra os termos.
danny117

5

Aqui está uma solução eficaz da didi para resolver esse problema. Como esse bug é muito comum e difícil de encontrar a causa, parece mais um problema do sistema. Por que não podemos ignorá-lo diretamente? É claro que podemos ignorá-lo. é o código de amostra:

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

Ao definir um manipulador de exceções não capturado padrão especial, o aplicativo pode alterar a maneira como as exceções não capturadas são tratadas para os segmentos que já aceitariam qualquer comportamento padrão fornecido pelo sistema. Quando um não capturado TimeoutExceptioné lançado de um encadeamento chamadoFinalizerWatchdogDaemon , esse manipulador especial bloqueia a cadeia do manipulador, o manipulador do sistema não será chamado e, portanto, a falha será evitada.

Através da prática, nenhum outro efeito ruim foi encontrado. O sistema do GC ainda está funcionando, os tempos limite são aliviados à medida que o uso da CPU diminui.

Para obter mais detalhes, consulte: https://mp.weixin.qq.com/s/uFcFYO2GtWWiblotem2bGg


4

Uma coisa que é invariavelmente verdadeira é que, neste momento, o dispositivo estaria sufocando alguma memória (que geralmente é o motivo do GC provavelmente ser acionado).

Como mencionado por quase todos os autores anteriormente, esse problema aparece quando o Android tenta executar o GC enquanto o aplicativo está em segundo plano. Na maioria dos casos em que o observamos, o usuário pausou o aplicativo bloqueando sua tela. Isso também pode indicar vazamento de memória em algum lugar do aplicativo ou o dispositivo já está carregado demais. Portanto, a única maneira legítima de minimizá-lo é:

  • para garantir que não haja vazamentos de memória e
  • para reduzir a pegada de memória do aplicativo em geral.

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

Isso não resolverá o problema caso a duração do sono seja> 100 segundos. Por que não configurá-lo para MAX_INT?
oba

Sim, eu estou apenas fazê exemplo ~
kot32

1
Isso não deve funcionar por causa de constantes inlining. Alterar o valor do campo não afetará o valor incorporado aos chamadores.
Hqzxzwb

0

O finalizeQueue pode ser muito longo

Eu acho que o Java pode exigir GC.SuppressFinalize () e GC.ReRegisterForFinalize () para permitir que o usuário reduza explicitamente o comprimento finalizedQueue

se o código fonte da JVM estiver disponível, podemos implementar esses métodos, como o ROM ROM Android


0

Parece um bug do Android Runtime. Parece haver um finalizador que é executado em seu thread separado e chama o método finalize () nos objetos se eles não estiverem no quadro atual do rastreamento de pilha. Por exemplo, o código a seguir (criado para verificar esse problema) terminou com a falha.

Vamos ter um cursor que faça algo no método finalize (por exemplo, SqlCipher, do close () que bloqueia o banco de dados que está sendo usado no momento)

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 fazemos algumas coisas de execução longa abrindo o cursor:

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

Isso causa o seguinte erro:

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)

A variante de produção com SqlCipher é muito semelhante:

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)

Resumo: Feche os cursores o mais rápido possível. Pelo menos no Samsung S8 com Android 7, onde o problema foi visto.


0

Para as classes que você cria (ou seja, não faz parte do Android), é possível evitar completamente a falha.

Qualquer classe que implementa finalize()tem alguma probabilidade inevitável de travar, conforme explicado pelo @oba. Portanto, em vez de usar finalizadores para executar a limpeza, use a PhantomReferenceQueue.

Por exemplo, confira a implementação no React Native: https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/jni/DestructorThread.java

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.