Introdução: "Quebrar não é construir"
\nTodo desenvolvedor Java já amaldiçoou um vazamento de memória (memory leak) em sua aplicação em produção. A aplicação cai com OutOfMemoryError, o servidor reinicia, os usuários escrevem e-mails furiosos. Mas há também o outro lado da moeda: como criar um vazamento de memória em Java intencionalmente? Parece loucura, mas foi exatamente essa pergunta (original do Stack Overflow) que reuniu milhares de visualizações.
Por que saber disso? Para entender a mecânica do coletor de lixo (GC) e os antipadrões que transformam seu código em uma peneira. Em 2025, quando o Java 21 com threads virtuais e Loom já é padrão, os vazamentos antigos não desapareceram — eles apenas se disfarçam sob novos recursos.
\nNeste artigo, não apenas mostraremos como "estragar" uma aplicação Java, mas também daremos as melhores práticas de detecção de vazamentos em 2025. Prontos? Vamos lá!
\n\nUm pouco de teoria: por que o Java não é "eterno"
\nMuitos iniciantes acreditam piamente que o coletor de lixo (GC) remove automaticamente tudo o que não é necessário. A realidade é cruel: vazamento de memória em Java é possível se um objeto permanece alcançável (reachable) a partir de raízes GC (GC roots), mas não é mais necessário para a aplicação.
\nEm 2025, com o surgimento dos Value Objects (Project Valhalla) e do ZGC aprimorado (coletor com tempo de pausa < 1 ms), a situação de desempenho melhorou, mas a lógica dos vazamentos permaneceu a mesma. O GC não é telepata — ele não sabe que você esqueceu de anular uma referência em um HashMap.
Método nº 1: Clássico — HashMap "esquecido"
\nO antipadrão mais popular, que passa de artigo em artigo. Mas sem ele, não dá para ir a lugar nenhum.
\nComo funciona o vazamento
\nVocê coloca objetos em um HashMap (ou HashSet) como chaves, mas esquece de removê-los. Se as chaves forem objetos mutáveis que alteram o código hash após a inserção, você terá garantia de um vazamento de memória Java.
import java.util.HashMap;\nimport java.util.Map;\n\npublic class LeakyMapExample {\n public static void main(String[] args) {\n Map<StringBuilder, String> map = new HashMap<>();\n\n for (int i = 0; i < 100_000; i++) {\n StringBuilder key = new StringBuilder("key_" + i);\n map.put(key, "value_" + i);\n // "Esqueceu" de remover — as chaves ficam para sempre\n }\n System.out.println("Tamanho do mapa: " + map.size());\n // Vazamento: 100_000 objetos StringBuilder + String no heap\n }\n}\n\n\nPor que isso é um vazamento? Enquanto a referência a map estiver viva, todas as chaves e valores permanecem no heap. Mesmo que a lógica da aplicação não use mais esses dados, o GC não os tocará.
Melhor prática 2025
\n- \n
- Use
WeakHashMapse as chaves forem objetos temporários. \n - Para cache, use
CaffeineouGuava Cachecom limites de tamanho/tempo. \n - Nunca use
StringBuildercomo chave — apenas tipos imutáveis. \n
Método nº 2: Coleções estáticas — o assassino silencioso
\nCampos estáticos vivem até o fim da JVM. Se você colocar dados em um static List ou static Map e não os limpar, terá um vazamento de memória Java no nível da classe.
import java.util.ArrayList;\nimport java.util.List;\n\npublic class StaticLeak {\n private static final List<byte[]> CACHE = new ArrayList<>();\n\n public static void addData() {\n // Cada chamada adiciona 10 MB à lista estática\n CACHE.add(new byte[10 * 1024 * 1024]);\n }\n\n public static void main(String[] args) throws Exception {\n for (int i = 0; i < 1_000; i++) {\n addData();\n Thread.sleep(100);\n }\n // OutOfMemoryError é inevitável\n }\n}\n\n\n<