Vazamento de memória: o que é, cenários típicos e diagnóstico

Autor: IT Sectr Publicado: 2026-07-29 Tempo de leitura: 10 min

Vazamento de memória (memory leak) — situação em que um aplicativo não libera a memória ocupada por objetos que não são mais necessários. No desenvolvimento móvel, isso é especialmente crítico: o heap limitado e a ausência de swap levam a OutOfMemoryError e falhas do aplicativo. De acordo com a Purdue University (2022), 35% dos aplicativos Android no Google Play contêm pelo menos um vazamento de memória. Vamos analisar cenários típicos, ferramentas de diagnóstico e métodos de correção.

Principais pontos

  • GC Root — ponto de entrada pelo qual o coletor de lixo determina objetos vivos
  • Vazamento de Context — passar Activity Context para um singleton retém toda a hierarquia de View
  • Handler com postDelayed — se a Activity for destruída, o Handler impede que ela seja coletada pelo GC
  • Heap dump — método principal de análise de vazamentos via MAT ou Android Profiler
  • SoftReference — alternativa ao WeakReference para caches com limpeza automática em caso de pouca memória

O que é um vazamento de memória em aplicativos móveis?

Vazamento de memória é uma situação em que a memória alocada não é retornada ao sistema depois que o objeto não é mais necessário para o programa. O coletor de lixo considera esse objeto como vivo porque há uma cadeia de referências ativa de um GC Root apontando para ele.

Em Java/Kotlin, o coletor de lixo funciona automaticamente, mas não consegue determinar que um objeto é logicamente desnecessário se houver uma referência técnica para ele. O desenvolvedor deve quebrar explicitamente as conexões desnecessárias. Em Swift/Objective-C, o ARC conta referências automaticamente, mas os retain cycles bloqueiam a diminuição do contador.

O principal perigo dos vazamentos é o efeito cumulativo. Cada vazamento consome uma pequena quantidade de memória, mas com transições repetidas entre telas (rotações de tela, abertura/fechamento de Activities), os vazamentos se acumulam até esgotar o limite do heap.

Como um vazamento difere de inchaço?

Vazamento — o objeto está inacessível ao código, mas não é removido pelo GC. Inchaço — o objeto é logicamente necessário, mas é armazenado em quantidade excessiva. Exemplo de inchaço: um cache de imagens de 100 MB com um conjunto de trabalho de 30 MB. Ambos os problemas levam a OOM, mas as causas e os métodos de tratamento são diferentes.

Como funciona o coletor de lixo e por que ocorrem vazamentos?

ART (Android Runtime) usa coleta de lixo geracional com compactação concorrente. A memória é dividida em geração jovem (Young), geração velha (Old) e objetos grandes (Large). Objetos que sobrevivem a vários ciclos de GC são movidos para a geração Old, onde a coleta ocorre com menos frequência — isso acelera os ciclos normais.

O GC começa quando o heap atinge um certo limite de ocupação (normalmente 75-85%). Durante o GC, todas as threads do aplicativo são pausadas (STW — Stop The World). Quanto mais objetos vivos, mais longa a pausa. Os vazamentos aumentam o número de objetos vivos, prolongando as pausas do GC.

O coletor determina objetos vivos percorrendo o gráfico a partir das GC Roots: campos estáticos, variáveis de pilha de threads ativas, referências JNI. Qualquer objeto alcançável por referências a partir dessas raízes é considerado vivo — mesmo que o desenvolvedor saiba que não é mais necessário.

kotlin
// Exemplo: coleção estática como GC Root — vazamento permanente
object GlobalHolder {
    val listeners = mutableListOf<WeakReference<Any>>()
}

class LeakingFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle ?= null) {
        super.onCreate(savedInstanceState)
        GlobalHolder.listeners.add(WeakReference(this))
        // WeakReference não impede o GC — comportamento correto
    }
}

WeakReference resolve o problema: o GC ignora referências fracas ao determinar objetos vivos. Se apenas referências fracas restarem para um objeto, ele será coletado no ciclo de GC mais próximo.

Cenários típicos de vazamentos em Android e iOS

Activity Context — o cenário de vazamento mais massivo no Android. Se um singleton, campo estático ou serviço de longa duração armazena uma referência a um Activity Context, toda a Activity com todas as suas Views não pode ser coletada pelo GC. Solução: use Application Context para objetos de longa duração.

Handler e mensagens enviadas — Handler.postDelayed(runnable, delay) coloca uma mensagem na fila do Main Looper. Se a Activity for destruída antes do término do atraso, a mensagem ainda está na fila e mantém uma referência através de Runnable → classe anônima → classe externa (Activity).

kotlin
class SafeActivity : AppCompatActivity() {
    private val mainHandler = Handler(Looper.getMainLooper())
    private val callback = Runnable { /* update UI */ }

    override fun onResume() {
        super.onResume()
        mainHandler.postDelayed(callback, 5000)
    }

    override fun onPause() {
        mainHandler.removeCallbacks(callback) // obrigatório: limpar fila
        super.onPause()
    }
}

Classes internas — uma classe interna não estática tem uma referência implícita à instância da classe externa. Se a classe externa for uma Activity e a classe interna for passada para algum lugar externo (por exemplo, para RecyclerView.Adapter), a Activity não pode ser coletada.

  • TimerTask e ScheduledExecutorService — tarefas agendadas antes da destruição da Activity
  • BroadcastReceiver — não registrado em onPause/onDestroy continua segurando o Context
  • ViewModel com referência a View — ViewModel sobrevive à Activity, a referência à View leva a vazamento
  • Retrofit Call — se o Call não for cancelado, a resposta chega a um Fragment destruído

Ferramentas de diagnóstico de vazamentos de memória

Android Studio Memory Profiler — ferramenta integrada para monitoramento de heap em tempo real. Mostra um gráfico de memória usada, número de alocações e objetos por tipo. Permite gravar um heap dump e exportá-lo no formato HPROF para análise no MAT.

Eclipse MAT (Memory Analyzer Tool) — analisador de desktop de heap dump. Constrói automaticamente relatórios de Leak Suspects, destacando objetos com o maior retained size e sugerindo a possível cadeia de GC Root para cada objeto suspeito.

Xcode Memory Graph Debugger — para iOS. Pausa o aplicativo e visualiza o gráfico de objetos. Retain cycles são destacados em vermelho; você pode clicar em qualquer objeto para ver seu retain count e referências.

FerramentaCapacidadesComplexidade
Memory ProfilerGráfico em tempo real, heap dump, Object Allocation TrackingBaixa
Eclipse MATDominator tree, Leak Suspects, consultas OQLMédia
LeakCanaryDetecção automática, trace de vazamento em notificaçãoMínima
Xcode Memory GraphGráfico visual de retain cycles, lista de objetos vivosBaixa

De acordo com o Uber Engineering Blog, a integração de perfilamento automático de memória (LeakCanary + análise de heap dump) no pipeline CI/CD reduz incidentes relacionados à memória em produção em 60% em 3 meses.

Métodos para eliminar vazamentos

Substituir Context — se um objeto sobreviver à Activity, use applicationContext. Todos os objetos de longa duração (singletons, repositórios, helpers de banco de dados) devem receber Application Context, não Activity Context. Exceção: componentes de UI que precisam de acesso ao tema ou recursos específicos da Activity.

Componentes conscientes do ciclo de vida — usar LifecycleObserver, DefaultLifecycleObserver ou extensões reativas cancela automaticamente as assinaturas em onDestroy. O Android Jetpack fornece lifecycleScope e viewModelScope, que são limpos pelo evento de ciclo de vida correspondente.

Classe interna estática — se a classe interna não precisar de acesso aos campos da classe externa, torne-a static. Uma classe interna estática não tem referência implícita à classe externa. Se o acesso for necessário, use WeakReference para a referência explícita.

kotlin
class MyActivity : AppCompatActivity() {

    // ❌ Classe interna não estática — referência implícita a MyActivity
    inner class BadListener : SomeListener {
        override fun onEvent() { /*...*/ }
    }

    // ✅ Classe interna estática — sem referência implícita
    class GoodListener(private val activityRef: WeakReference<MyActivity>) : SomeListener {
        override fun onEvent() { /*...*/ }
    }
}

Em iOS, use listas de captura: [weak self] em closures que podem sobreviver ao seu criador. Para delegados, use referências fracas (weak var delegate). Para closures que são garantidas de serem chamadas apenas durante a vida de self, [unowned self] pode ser usado, mas com cuidado — acessar um objeto desalocado causará uma falha.

Perguntas frequentes

Como encontrar um vazamento sem ferramentas especiais?

No Android, execute várias transições entre telas (Activity A → B → A → B) e verifique adb shell dumpsys meminfo package_name. Se o Total PSS crescer de forma estável e não retornar ao valor original — há um vazamento. No iOS, similarmente: use Debug Memory Graph no Xcode para inspeção visual.

Uma corrotina Kotlin pode causar um vazamento?

Sim, se o CoroutineScope não for cancelado quando o componente for destruído. Uma corrotina lançada em GlobalScope continua executando mesmo após finish() da Activity. Solução: use viewModelScope (cancelado em onCleared) ou lifecycleScope (cancelado em onDestroy). Para scopes personalizados, crie scopes conscientes do ciclo de vida através de LifecycleOwner.

Como o Bitmap afeta os vazamentos?

Bitmap armazena dados de pixel no heap nativo, não no heap Java. Isso significa que o GC Java não vê o tamanho real do Bitmap. Se o Bitmap não for reciclado com recycle() ou a referência não for anulada, a memória nativa não será liberada. Use BitmapFactory com inSampleSize para carregar cópias reduzidas e Glide/Coil para gerenciamento automático de cache.

O que é um vazamento através de campo estático?

Um campo estático é uma GC Root. Ele vive enquanto a classe estiver carregada (no Android — enquanto o Process estiver vivo). Se um campo estático referenciar uma Activity, Bitmap, View ou qualquer outro objeto pesado, esse objeto nunca será coletado pelo GC. Um campo estático é uma referência eterna. Solução: armazene apenas WeakReference ou anule o campo estático em onDestroy.

Como evitar vazamentos no iOS com ARC?

ARC libera objetos automaticamente quando a contagem de referências fortes chega a zero. Um retain cycle é a única forma de vazamento com ARC. Sempre use weak para referências pai→filho onde o filho pode sobreviver ao pai (delegados, data sources). Para closures, use a lista de captura [weak self] e verifique se self não é nil dentro da closure.

Resumo

  • Vazamento de memória — um objeto está inacessível ao código, mas não é removido pelo GC porque há uma referência ativa de uma GC Root
  • GC Roots incluem campos estáticos, variáveis de pilha e referências JNI; qualquer objeto alcançável a partir deles está vivo
  • Vazamento de Context — o problema mais difundido no Android: passar Activity Context para um singleton ou campo estático
  • Handler e classe interna — a segunda causa mais comum: mensagens não canceladas na fila do Looper mantêm uma referência à Activity
  • LeakCanary — a ferramenta padrão de detecção automática; faz um heap dump e mostra a cadeia exata de GC Root
  • lifecycleScope e viewModelScope resolvem o problema de vazamentos por corrotinas — cancelamento automático na destruição
  • Perfile a memória no CI/CD: LeakCanary em debug + análise de heap dump em execuções de teste devem bloquear o merge em novos vazamentos

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.

Discutir o projeto

Leia também