O gerenciamento automático de memória através da coleta de lixo é um mecanismo chave da plataforma Android, baseado na máquina virtual ART. De acordo com Google Android Documentation, 2026, o coletor de lixo libera o desenvolvedor do gerenciamento manual de memória, removendo automaticamente objetos que não têm mais referências. Sem GC, cada alocação de objeto exigiria uma chamada explícita a free ou delete, o que no ecossistema Java com milhões de objetos por segundo é fisicamente impossível.
Pontos principais
Garbage Collection (GC) é um processo automático de detecção e liberação de memória ocupada por objetos que não são mais usados pelo programa. No contexto do desenvolvimento mobile, o GC é usado na plataforma Android através da máquina virtual ART, bem como na Java Virtual Machine padrão.
Ao contrário de linguagens com gerenciamento manual de memória (C, C++), onde o programador deve chamar explicitamente free ou delete, o GC assume completamente a tarefa de rastrear o ciclo de vida dos objetos. O desenvolvedor cria novos objetos através do operador new, enquanto o coletor determina quando um objeto se torna inalcançável — ou seja, quando não restam referências ativas a ele.
As principais métricas de eficiência do GC são o tempo de pausa (pause time) e a taxa de transferência (throughput). A pausa é o período durante o qual a execução da aplicação é interrompida para realizar a coleta. Em um ambiente mobile, pausas superiores a 8–16 milissegundos são perceptíveis como quadros perdidos (jank).
De acordo com o Google I/O 2019, o ART no Android 10 reduziu as pausas típicas de GC para 2–4 ms, o que representa 70% menos em comparação com o Dalvik no Android 4.4. No entanto, o gerenciamento inadequado de memória — alocação frequente de objetos em loops, criação de instâncias temporárias desnecessárias — continua sendo a principal causa de problemas de desempenho.
Todas as implementações de GC em Java e Android baseiam-se em vários algoritmos fundamentais que são combinados para alcançar um equilíbrio entre tempo de pausa e completeza da limpeza. Compreender esses algoritmos é essencial para escrever código amigável ao GC.
Mark-and-Sweep é o algoritmo mais simples, funcionando em duas etapas. Na fase de Mark, o coletor percorre o grafo de objetos começando pelas referências raiz (root set) — variáveis locais, campos estáticos, pilhas de threads. Cada objeto alcançável é marcado com um sinalizador de vivo. Na fase de Sweep, o coletor percorre todo o heap e libera a memória dos objetos não marcados.
A desvantagem é a fragmentação da memória: após o Sweep, as áreas livres alternam-se com as ocupadas, dificultando a alocação de objetos grandes. Em cenários mobile, isso é crítico, pois o heap geralmente é pequeno (64–512 MB no Android).
Copying Collection divide o heap em dois semiespaços (semi-spaces). Objetos ativos são copiados compactamente de um semiespaço para o outro, sem lacunas. Após a cópia, o semiespaço antigo é declarado completamente livre. O algoritmo elimina totalmente a fragmentação, mas requer o dobro de memória.
Em ambientes mobile, o Copying Collection é usado por coletores geracionais para limpeza rápida de objetos jovens, que estatisticamente morrem cedo (hipótese geracional fraca).
Generational Collection divide o heap em gerações: Young Generation (objetos jovens) e Old Generation (objetos velhos que sobreviveram a várias coletas). A coleta da geração jovem (Minor GC) é realizada com frequência e rapidez, pois a maioria dos objetos morre jovem. A coleta da geração velha (Major GC ou Full GC) ocorre com menos frequência, mas dura mais tempo.
// Demonstração de GC geracional: objetos jovens morrem rápido
void processItems(List<Item> items) {
List<Result> results = new ArrayList<>(); // vive durante todo o método
for (Item item : items) {
Result r = new Result(item.getValue()); // morre instantaneamente
if (r.isValid()) {
process(r); // r torna-se lixo
}
}
saveResults(results); // results passa para Old Gen
}
Neste exemplo, objetos Result são criados dentro de um loop e tornam-se imediatamente lixo — são candidatos ideais para Young GC. O objeto results vive mais tempo e migra para Old Generation. A separação de gerações permite que o Minor GC limpe objetos jovens em milissegundos sem tocar no heap antigo.
O Android evoluiu de Dalvik VM para ART (Android Runtime), e a implementação do GC é uma das principais diferenças entre eles. Compreender a arquitetura do GC no Android ajuda a escrever código que minimize as pausas em dispositivos reais.
| Característica | Dalvik (até 4.4) | ART (5.0+) |
|---|---|---|
| Tipo de GC | Mark-and-Sweep com Concurrent Mark | Generacional + Concorrente |
| Pausa típica | 10–30 ms | 2–4 ms |
| Compactação | Não (apenas a fragmentação cresce) | Sim (em segundo plano, sem parar o app) |
| Compilação AOT | JIT (Just-In-Time) | AOT + JIT (híbrido) |
Dalvik usava uma combinação de Mark-and-Sweep com uma fase concorrente. O Concurrent Mark permitia que a aplicação continuasse funcionando durante a travessia do grafo de objetos, mas a fase Sweep exigia a parada de todas as threads (Stop-The-World). Em dispositivos com pouca RAM (512 MB — 1 GB), as pausas atingiam 30 ms, causando atrasos perceptíveis na interface. Além disso, o Dalvik não compactava o heap, portanto, após uso prolongado, a fragmentação aumentava e a alocação de objetos grandes (por exemplo, Bitmap) podia lançar OutOfMemoryError mesmo com memória livre total suficiente.
ART (Android Runtime) introduziu um coletor geracional com compactação concorrente. O heap é dividido em três regiões: Young, Mature (análogo à Old Generation) e Large Object Space (para objetos maiores que 12 KB). A coleta da região Young ocorre em paralelo sem parar as threads na maioria dos casos. No Android 10+, foi introduzido o Concurrent Copying — a compactação é executada em uma thread de fundo sem Stop-The-World.
Graças à arquitetura do ART, as pausas típicas de GC foram reduzidas para 2–4 ms, e em cenários com predominância de objetos jovens — para 0.5–1 ms. Isso permitiu que dispositivos Android mantivessem 60 FPS estáveis mesmo durante operações ativas de memória.
No ecossistema Java, existem várias implementações de GC, cada uma com seu próprio perfil de desempenho. Para o desenvolvimento Android, a escolha é limitada ao ART, mas o conhecimento do Java GC é útil ao escrever código do lado do servidor para aplicações mobile e ao desenvolver com Kotlin Multiplatform.
Serial GC é um coletor de thread única com parada completa da aplicação (Stop-The-World). Cada operação Mark, Sweep e Compact é realizada por uma thread. O desempenho é baixo — não é usado para servidores mobile. Adequado apenas para aplicações pequenas com heap de até 100 MB.
Parallel GC (também conhecido como Throughput Collector) usa múltiplas threads para todas as fases de coleta. É orientado para a máxima taxa de transferência (throughput) — minimizando o tempo gasto em GC em relação ao tempo de execução da aplicação. Ativado através da flag -XX:+UseParallelGC na JVM.
G1 (Garbage-First) GC é o coletor padrão no Java 9+. O heap é dividido em regiões de 1–32 MB. O G1 prevê o tempo de pausa e se esforça para permanecer dentro do limite especificado (padrão 200 ms). Prioridade: regiões com maior quantidade de lixo são limpas primeiro (daí o nome). O G1 é eficaz para servidores com grandes heaps (4–64 GB) com pausas previsíveis.
// Ativando G1 GC com pausa alvo de 100 ms
// java -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -jar app.jar
public class MemoryMonitor {
private static final long THRESHOLD = 512 * 1024 * 1024; // 512 MB
public void checkHeapUsage() {
Runtime rt = Runtime.getRuntime();
long used = rt.totalMemory() - rt.freeMemory();
if (used > THRESHOLD) {
System.out.println("Heap usage exceeded threshold: " + used);
System.out.println("Consider reducing allocations");
}
}
}
Monitorar o heap via Runtime permite detectar vazamentos de memória em estágio inicial. Se used exceder 80% do heap máximo em operação estável — isso é um sinal de possível vazamento ou consumo excessivo de memória pela aplicação.
Mesmo o ART GC moderno não resolve todos os problemas — o uso inadequado de memória continua sendo a principal causa de jank e ANR (Application Not Responding). Vamos examinar os principais cenários e métodos de otimização.
Pausas de GC — paradas das threads da aplicação durante a coleta. Na tela, isso se manifesta como quadros perdidos, quando o tempo entre dois quadros excede 16.6 ms (60 FPS). Se o GC durar 30 ms, apenas um quadro é desenhado em vez de dois — o usuário vê travamentos na interface.
As principais causas de pausas longas: grande número de objetos vivos na Old Generation, fragmentação do heap, Full GC frequente. Para diagnóstico, são usados Android Studio Profiler e systrace.
A regra principal do código amigável ao GC é minimizar o número de objetos alocados. Cada novo objeto requer não apenas alocação de memória, mas também coleta subsequente. Mesmo que o GC seja rápido, 1000 alocações extras por segundo resultam em 1000 verificações para o coletor.
Um vazamento de memória ocorre quando um objeto permanece alcançável mesmo não sendo mais necessário. O GC não pode excluir tal objeto e a memória se esgota gradualmente. Causas típicas: listeners não removidos, referências estáticas a Activity, classes anônimas capturando contexto externo e Cursor/InputStream não fechados.
// Vazamento de memória: classe anônima mantém referência à Activity
public void startTask() {
new Thread(new Runnable() { // mantém implicitamente this (Activity)
@Override
public void run() {
// operação longa...
System.out.println("Done");
}
}).start();
}
// Correção: classe estática aninhada + WeakReference
private static class TaskRunnable implements Runnable {
private WeakReference<Activity> activityRef;
TaskRunnable(Activity activity) {
this.activityRef = new WeakReference<>(activity);
}
@Override
public void run() {
Activity act = activityRef.get();
if (act != null) {
// trabalho seguro com Activity
}
}
}
Neste exemplo, o Runnable anônimo captura uma referência implícita à Activity. Enquanto a thread estiver viva — a Activity não pode ser coletada pelo GC, mesmo que o usuário já tenha fechado a tela. A correção com WeakReference + classe estática quebra essa corrente e permite que a Activity seja liberada.
Perguntas frequentes
GC no Android (ART) é um coletor geracional com compactação concorrente, otimizado para dispositivos mobile com memória limitada. Java GC (G1, ZGC) são coletores do lado do servidor com grandes heaps e pausas previsíveis. ART GC não usa flags da JVM — toda a configuração é feita automaticamente no nível do SO.
Stop-The-World é o momento em que o coletor pausa todas as threads da aplicação para percorrer o grafo de objetos ou liberar memória com segurança. Quanto mais longo o STW, mais perceptível é o jank. O ART reduziu o tempo típico de STW para 2–4 ms graças à sua arquitetura geracional.
Use o Android Studio Memory Profiler — ele mostra o crescimento do heap, a quantidade de alocações e permite fazer Heap Dump. Para análise aprofundada, use LeakCanary — a biblioteca detecta automaticamente vazamentos e mostra a cadeia de referências que impede a coleta do GC.
Full GC é uma coleta completa de todas as gerações do heap, incluindo a Old Generation. Em aplicações mobile, o Full GC pode durar 50–200 ms, causando jank ou ANR perceptíveis. Principais causas: fragmentação do heap, vazamentos de memória, exceder o limite da Old Generation.
Kotlin fornece corrotinas com concorrência estruturada — o cancelamento do escopo cancela automaticamente todas as corrotinas filhas, prevenindo vazamentos. Kotlin também tem o delegado lazy para inicialização preguiçosa e funções de escopo que reduzem o número de objetos temporários.
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