CodeGym /Cursos /JAVA 25 SELF /Análise de erros comuns ao lidar com memória

Análise de erros comuns ao lidar com memória

JAVA 25 SELF
Nível 64 , Lição 4
Disponível

1. Erros comuns ao lidar com memória

É hora de olhar o outro lado da “magia” do gerenciamento automático de memória. Mesmo que você não programe em C, onde é preciso controlar cada byte, ainda é possível fazer besteira em Java a ponto de o aplicativo devorar memória como um gato faminto — salsicha. Vamos analisar os erros mais comuns e como evitá‑los.

Ouvintes esquecidos (listeners)

Em Java, o padrão “listener” é usado com frequência — um objeto que assina eventos de outro objeto. Por exemplo, você criou um botão e adicionou um manipulador de clique:

button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        // tratamento de clique
    }
});

Problema: se você esquecer de remover esse ouvinte (removeActionListener) quando o botão ou a janela não forem mais necessários, o ouvinte continuará pendurado na memória. Mesmo que você feche a janela e zere todas as referências a ela, o objeto‑ouvinte ainda mantém uma referência à janela (ou vice‑versa), impedindo o coletor de lixo de liberar a memória.

Analogia: Imagine que você se mudou, mas esqueceu de cancelar a newsletter da pizzaria — continuam enviando propaganda para o endereço antigo.

Coleções estáticas que não são limpas

Campos estáticos vivem tanto quanto a classe (e às vezes — até o fim do aplicativo). Se você tem uma coleção estática:

public class Cache {
    public static final List<String> globalList = new ArrayList<>();
}

e adiciona objetos ali sem removê‑los, eles vão ficar na memória para sempre. Mesmo que não haja mais referências a esses objetos em outro lugar, a referência a partir da coleção estática não permitirá que o GC os remova.

Exemplo real: Cache de fotos em um aplicativo desktop que nunca é limpo. Após algumas horas de uso — OutOfMemoryError.

Não liberação de recursos (arquivos, fluxos, conexões)

Embora o Java libere memória, ele não faz o fechamento automático de descritores de arquivo, conexões de rede e outros recursos externos. Se você esquecer de fechar um arquivo ou um fluxo, o recurso ficará ocupado e, em algum momento, o sistema dirá: “Chega, não há mais arquivos!” (IOException: Too many open files).

Dica: Sempre use try-with-resources:

try (FileInputStream in = new FileInputStream("data.txt")) {
    // lendo o arquivo
} // in.close() será chamado automaticamente!

Objetos grandes que permanecem muito tempo na memória

Às vezes você cria um array grande ou uma coleção, usa e depois “esquece de soltar”. Por exemplo:

List<byte[]> bigList = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
    bigList.add(new byte[1024 * 1024]); // 1 MB cada
}
// ... esquecemos de limpar bigList

Se essa coleção viver em um campo estático ou em um objeto que demora a ser removido, toda essa memória continuará ocupada.

Classes internas e anônimas: captura de referências externas

Classes anônimas (e internas) em Java armazenam uma referência implícita ao objeto externo:

public class Outer {
    void doSomething() {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello from inner!");
            }
        };
        // r é armazenado em algum lugar
    }
}

Se o objeto r for parar em uma coleção estática ou em um cache, ele vai “segurar” uma referência à instância de Outer, mesmo que ela já não seja necessária. Resultado — vazamento de memória. Com expressões lambda a situação é um pouco melhor, mas se a lambda usar campos da classe externa, a referência ainda será preservada.

2. Erros ao lidar com o coletor de lixo

Chamada forçada de System.gc()

Muitos iniciantes pensam: “A memória está acabando — vou chamar System.gc() e tudo se resolve!”. Na prática, isso é apenas um pedido para a JVM, não uma garantia de coleta imediata. O uso frequente pode piorar bastante o desempenho, causar longas pausas e travadinhas. Em aplicativos reais, é melhor confiar na JVM — ela decide quando coletar o lixo. Aliás, algumas JVMs podem ignorar chamadas explícitas de GC (por exemplo, com a opção -XX:+DisableExplicitGC).

Ignorar logs do GC

Nos logs do GC, dá para ver quando ocorrem as coletas, quanto tempo levam e quanta memória é liberada. Se você não olhar esses logs, pode perder sinais de problemas: pausas longas, Full GCs frequentes, vazamentos de memória.

Como ativar os logs do GC:

java -Xlog:gc* -jar MyApp.jar

ou para JVMs antigas:

java -XX:+PrintGCDetails -XX:+PrintGCDateStamps -jar MyApp.jar

Escolha incorreta do GC para o caso de uso

A escolha do coletor afeta a latência e a estabilidade. Para baixa latência (bolsas, jogos online), o Parallel GC stop‑the‑world é uma má ideia: ele pode “congelar” todas as threads durante a coleta. Considere G1 GC, ZGC ou Shenandoah.

3. Erros com coleções

Usar HashMap em vez de WeakHashMap para caches.
Se você está fazendo um cache em que os objetos devem ser removidos automaticamente quando não houver mais referências “vivas” a eles, use WeakHashMap:

Map<Key, Value> cache = new WeakHashMap<>();

Com um HashMap comum, os objetos viverão até que o cache seja limpo manualmente, o que levará a vazamento de memória.

Chamadas de remove() esquecidas para elementos.
Se você adiciona objetos a coleções (por exemplo, listas de ouvintes), mas não os remove quando não são mais necessários, esses objetos viverão para sempre, especialmente em coleções de longa vida (por exemplo, estáticas).

4. Boas práticas: como evitar problemas

Sempre remova os ouvintes.
Se um objeto assinou eventos, certifique‑se de removê‑lo quando não for mais necessário. É prático fazer isso no método dispose() ou ao fechar a janela/tela.

button.removeActionListener(myListener);

Use referências fracas para caches.
Se no cache é possível abrir mão da garantia de retenção do objeto, use WeakReference ou coleções baseadas nelas (WeakHashMap). Assim, o GC poderá liberar memória quando necessário.

Monitore a memória em produção.
Use jvisualvm, jconsole ou sistemas de APM. Isso ajuda a detectar vazamentos antes das reclamações dos usuários.

Analise o heap dump quando suspeitar de vazamento

Se o aplicativo começou a consumir mais memória do que o normal, gere um heap dump (por exemplo, via jmap ou jvisualvm) e veja quais objetos ocupam mais espaço. Frequentemente, o culpado aparece em poucos minutos.

Ajuste os parâmetros da JVM.

  • -Xmx — tamanho máximo do heap
  • -Xms — tamanho inicial do heap

Limites razoáveis ajudam a evitar OutOfMemoryError e aceleram o diagnóstico.

5. Prática: exemplo de código com vazamento de memória e sua correção

Exemplo 1: Vazamento por meio de uma coleção estática

public class MemoryLeakDemo {
    // Coleção estática — vive indefinidamente
    private static final List<byte[]> leakyList = new ArrayList<>();

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            leakyList.add(new byte[1024 * 1024]); // 1 MB a cada vez
            System.out.println("Adicionado " + (i + 1) + " MB");
        }
        // OutOfMemoryError!
    }
}

Correção: Use uma variável local ou limpe a coleção quando ela não for mais necessária.

public class MemoryLeakFixed {
    public static void main(String[] args) {
        List<byte[]> tempList = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            tempList.add(new byte[1024 * 1024]);
            System.out.println("Adicionado " + (i + 1) + " MB");
        }
        // tempList = null; // Pode anular explicitamente
        // Agora os objetos ficam disponíveis para o GC após sair do método
    }
}

Exemplo 2: Vazamento via ouvinte

public class Window {
    private final List<EventListener> listeners = new ArrayList<>();

    public void addListener(EventListener l) {
        listeners.add(l);
    }
    // Não há método removeListener!
}

Correção: Adicione um método para remover o ouvinte e chame‑o ao fechar a janela.

public void removeListener(EventListener l) {
    listeners.remove(l);
}

Exemplo 3: Cache com HashMap em vez de WeakHashMap

Map<Object, Object> cache = new HashMap<>();
// ... adicionamos objetos

Correção: Migre para WeakHashMap:

Map<Object, Object> cache = new WeakHashMap<>();

Dicas de configuração da JVM para monitorar memória

  • Ative os logs do GC: -Xlog:gc* ou -XX:+PrintGCDetails
  • Limite o tamanho máximo do heap: -Xmx512m
  • Se usar caches — monitore o tamanho e aplique referências fracas quando for adequado
  • Experimente coletores de GC: -XX:+UseG1GC, -XX:+UseZGC, -XX:+UseShenandoahGC

7. Erros comuns ao lidar com memória

Erro nº 1: Ouvintes e assinaturas esquecidos. Se você adicionou um ouvinte a um objeto, mas esqueceu de removê‑lo, o objeto‑ouvinte (e tudo ao que ele se refere) permanecerá na memória. Clássico em GUI e sistemas baseados em eventos. Use removeListener/removeActionListener.

Erro nº 2: Coleções estáticas sem limpeza. Campos estáticos vivem por muito tempo. Se você coloca objetos neles e não limpa a coleção, esses objetos ficarão na memória para sempre. Especialmente traiçoeiro em caches sem limite.

Erro nº 3: Não liberação de recursos externos. Deixou um fluxo, arquivo ou conexão aberto? Você desperdiça memória e esbarra nos limites do SO. Use try-with-resources e feche os recursos.

Erro nº 4: Chamada forçada de System.gc(). Isso não é panaceia, é apenas um pedido para a JVM. Frequentemente leva a pausas e degradação de desempenho.

Erro nº 5: Coleções comuns para caches. Se os objetos no cache devem ser removidos sozinhos, use referências fracas/soft (WeakHashMap, SoftReference). Caso contrário, você terá vazamento.

Erro nº 6: Classes internas e anônimas que capturam referências externas. Classes internas e lambdas podem reter implicitamente uma referência ao objeto externo. Se forem salvas em uma coleção de longa vida — isso é um vazamento.

Erro nº 7: Ignorar logs do GC. Se você não olhar os logs do GC, não saberá sobre pausas longas ou Full GCs frequentes — e os usuários saberão pelos engasgos e travadas. Ative -Xlog:gc* ou -XX:+PrintGCDetails.

1
Pesquisa/teste
Memória e coleta de lixo, nível 64, lição 4
Indisponível
Memória e coleta de lixo
Memória e coleta de lixo
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION