Um vazamento de memória (Memory Leak) é uma situação em que o aplicativo mantém referências a objetos que não são mais necessários, impedindo o coletor de lixo de liberar a memória ocupada. Segundo o LeakCanary, mesmo em aplicativos bem escritos ocorrem de 3 a 5 vazamentos a cada 10.000 linhas de código. Cada vazamento reduz gradualmente a memória disponível, levando a lentidão e OutOfMemoryError.
Principais pontos
Um vazamento de memória (Memory Leak) é uma situação em que um objeto permanece acessível através de uma cadeia de referências fortes (Strong Reference), embora logicamente não seja mais necessário para o aplicativo. O coletor de lixo (GC) considera tal objeto como vivo e não libera a memória que ele ocupa. Como resultado, a memória disponível do heap diminui constantemente e a frequência de pausas do GC aumenta.
Ao contrário de linguagens com gerenciamento manual de memória (C, C++), em Java/Kotlin um vazamento não é um free() esquecido, mas uma referência esquecida. Enquanto existir uma strong reference de uma GC Root até o objeto vazado, o GC o considera necessário. GC Roots típicas: campos estáticos, threads ativas, pilha de chamadas, referências globais JNI.
O perigo dos vazamentos é seu efeito cumulativo. Um vazamento de 100 KB é imperceptível, mas 100 desses vazamentos ocupam 10 MB, e o aplicativo começa a ficar lento devido a GCs frequentes. Uma massa crítica de vazamentos leva a OutOfMemoryError e à queda do aplicativo. Sintomas de um vazamento: crescimento constante do consumo de memória no gráfico do Profiler, pausas frequentes do GC com STW (Stop The World) e degradação do desempenho da UI.
Cinco tipos de vazamentos cobrem 95% dos casos no desenvolvimento móvel. Cada um tem sua própria causa e padrão de código característico.
O mais conhecido vazamento no Android é armazenar uma referência estática a uma Activity ou Context. Código típico: um campo estático Activity que não é anulado em onDestroy(). Enquanto o campo estático viver, toda a Activity vive com sua árvore de View, que pode ocupar de 1 a 10 MB. Este é o vazamento clássico que o LeakCanary encontra primeiro.
Solução: nunca armazene Activity ou Context em campos estáticos. Use Application Context para singletons que sobrevivem à Activity. Se precisar de uma referência a uma Activity, use WeakReference<Activity>.
object MySingleton {
private var weakActivity: WeakReference<Activity>? = null
fun attach(activity: Activity) {
weakActivity = WeakReference(activity)
}
}
Classes anônimas e classes internas não estáticas mantêm implicitamente uma referência à classe contêiner. Um Runnable passado para um Handler que executa após onDestroy() retém toda a Activity. Um callback do Retrofit que captura uma Activity faz o mesmo. Este é o tipo mais insidioso de vazamento — a referência implícita não é visível no código.
Expressões object e lambdas do Kotlin também capturam referências à classe externa. Torne as classes internas estáticas (ou de nível superior em Kotlin) e passe referências externas através de WeakReference. Para lambdas, use uma abordagem Lifecycle-aware com viewLifecycleOwner.
Assinar serviços do sistema sem cancelar a assinatura é um vazamento direto. SensorManager, LocationManager, NotificationListener registrados em onResume() sem chamar unregister em onPause() retêm a Activity. Da mesma forma: um Disposable do RxJava não adicionado a um CompositeDisposable e uma corrotina lançada através de GlobalScope.
Use componentes Lifecycle-aware: observe() com LifecycleOwner cancela a assinatura automaticamente em onDestroy(). Para RxJava — viewLifecycleOwner.lifecycle.addObserver com DisposableObserver. Para corrotinas — lifecycleScope.launch() está vinculado ao ciclo de vida.
// cancelamento automático de assinatura via Lifecycle
viewModel.userData.observe(viewLifecycleOwner) { data ->
updateUI(data)
}
// corrotinas com lifecycleScope
lifecycleScope.launch {
viewModel.loadData().collect { render(it) }
}
Um Bitmap ocupa uma quantidade significativa de memória do heap: um bitmap FullHD são 1920 × 1080 × 4 bytes = 8,3 MB. Se um Bitmap for criado para cada item da lista e recycle() não for chamado ao ocultar, a memória se esgota rapidamente. Em versões antigas do Android (anteriores a 3.0), o Bitmap era armazenado na memória nativa, mas nas modernas está no heap Dalvik/ART, e o GC só pode liberá-lo se não houver uma strong reference.
Use Glide ou Coil para carregar imagens — essas bibliotecas gerenciam cache e reciclagem automaticamente. Se trabalhar diretamente com Bitmap, chame bitmap.recycle() para imagens grandes que não são mais exibidas e use inSampleSize para carregar cópias reduzidas.
Um Fragment tem dois ciclos de vida: o do próprio Fragment e o de sua View. Após onDestroyView(), a árvore View é destruída, mas o próprio Fragment pode permanecer na memória se houver uma referência externa. Um erro típico é armazenar uma referência a um Fragment em um adaptador ViewPager ou em um gráfico de navegação que não é limpo ao ser destruído.
Nunca armazene uma referência a um Fragment nos campos de objetos de longa duração. Use childFragmentManager para fragments aninhados e observe() com LifecycleOwner para transferência de dados entre eles. O ViewPager2 resolveu esse problema no nível da API: FragmentTransactionAdapter gerencia corretamente o ciclo de vida.
A detecção de um vazamento requer verificar dois fatos: a memória não retorna após o tempo de vida esperado e a quantidade de objetos de um determinado tipo cresce sem diminuir. O processo de diagnóstico inclui três etapas.
A primeira etapa é uma verificação visual através do Memory Profiler no Android Studio. Abra a guia Memory, execute a ação desejada (abra e feche a tela), pressione GC (Garbage Collection) e veja se a memória retorna ao nível inicial. Se após 3–4 ciclos de abertura-fechamento a memória crescer consistentemente — há um vazamento.
A segunda etapa é fazer um Heap Dump. No Memory Profiler, pressione Dump Java Heap. Abra o arquivo .hprof resultante no Android Studio: você verá todos os objetos no heap com tamanhos e referências. Procure por classes cuja contagem deve ser zero após fechar a tela. Por exemplo, MainActivity com contagem 2 após o fechamento é um vazamento óbvio.
A terceira etapa é analisar o Retained Size e a GC Root. No Android Studio, analise o Retained Size: quanta memória será liberada se você remover este objeto. O caminho da GC Root até o objeto mostra o que o está segurando: Static field → HashMap → Activity — e você vê o ponto do vazamento. O painel Reference mostra todos os detentores do objeto.
Quatro ferramentas cobrem a busca de vazamentos desde a detecção automática até a análise profunda de Heap Dump.
| Ferramenta | Método | Formato dos resultados |
|---|---|---|
| LeakCanary | Monitoramento automático | Heap Dump + stack trace do vazamento |
| Android Memory Profiler | Monitoramento manual | Gráfico de memória + Heap Dump |
| MAT (Eclipse) | Análise profunda | Relatório Dominator Tree + caminho GC Root |
| Perfetto | Rastreamento de sistema | Linha do tempo + memória nativa |
LeakCanary é indispensável para qualquer projeto Android. Ele detecta automaticamente vazamentos no final do ciclo de vida de Activity/Fragment e mostra o local exato do vazamento com um stack trace. Integração: uma linha no build.gradle. LeakCanary 2.x não requer inicialização manual — ele registra automaticamente o Application Watcher.
A prevenção de vazamentos é incorporada ao processo de desenvolvimento através de um conjunto de regras e ferramentas que verificam o código em cada etapa.
Nunca armazene uma referência a uma Activity, Fragment ou View em um campo estático, singleton ou objeto de longa duração. Se a referência for inevitável, use WeakReference ou armazene dados através de ViewModel, que vive exatamente o tempo necessário e não retém uma View diretamente.
ViewModel e LiveData do Android Architecture Components resolvem o problema do ciclo de vida no nível da arquitetura. ViewModel sobrevive à rotação da tela e não contém referências a View. LiveData cancela automaticamente a assinatura do observer em onDestroy(). Use-os em vez de assinatura manual em serviços do sistema.
Durante a revisão de código, preste atenção a: campos estáticos com tipos Context/View, classes anônimas, lambdas capturando Activity, assinaturas manuais, RxJava disposable sem composite, armazenamento de Fragment via Bundle. Em Kotlin, verifique adicionalmente corrotinas com launch sem vinculação ao ciclo de vida.
LeakCanary pode funcionar como parte do pipeline de testes: execute testes de aceitação com LeakCanary e falhe a compilação se um vazamento for encontrado. Isso impede que vazamentos cheguem à produção. Complemente a verificação com a regra StaticFieldLeak do Android Lint — ela encontra vazamentos potenciais no nível de análise estática.
// LeakCanary em testes
class LeakTest {
@Test
fun activityShouldNotLeak() {
ActivityScenario.launch(MainActivity::class.java)
.close()
LeakAssertions.assertNoLeak() // falhar se houver um vazamento
}
}
Perguntas frequentes
O vazamento é a causa, e OutOfMemoryError é a consequência. Um vazamento não leva ao OOM, mas o acúmulo de dezenas de vazamentos esgota o Heap. OOM é uma exceção fatal, enquanto o vazamento é um padrão que leva a ela com o tempo.
Através do Android Memory Profiler: abra e feche a tela 5 vezes, após cada fechamento invoque o GC. Se a memória não voltar ao nível base — há um vazamento. Faça um Heap Dump e encontre na lista a classe Activity cuja contagem seja maior que 0 após o fechamento.
Parcialmente. Kotlin resolve o problema de null-safety, mas não gerencia strong references. Corrotinas com lifecycleScope e viewModelScope previnem vazamentos de tarefas em segundo plano, enquanto sealed class e data class reduzem o número de estados que levam a vazamentos. A proteção principal são padrões arquiteturais, não características da linguagem.
LeakCanary às vezes dá falsos positivos: um objeto pode ser temporariamente retido pelo sistema (por exemplo, InputMethodManager retém a última View). Verifique manualmente: se o Retained Size for < 1 KB e a GC Root for um serviço do sistema, provavelmente é um falso positivo.
Não. Vazamentos são possíveis em qualquer plataforma com GC: iOS (Swift/Objective-C), Flutter (Dart), navegadores web (JavaScript). Os mecanismos são os mesmos — strong reference de uma GC Root. No iOS, o ARC gerencia a memória automaticamente, mas retain cycles entre objetos criam o mesmo vazamento.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também