Limpar Memória

Bom dia, esses dias estou instanciando 3000 objetos no meu arrayList, isso ocupa 2g de memória, logo que eu dou clear() no meu arrayList e chamo o garbage coletor, mesmo sem o arrayList não fazer referência a nada, o sistema ainda continua a ocupar 2g, alguém já passou por isso?

Você não consegue chamar explicitamente o GC.
O método Sysyem.gc() só sinaliza a VM para tentar executar o GC assim que possível.

3000 objetos é pouca coisa.
Como está a estrutura dessa classe?
Porque é tão “gordinha”?

Posta o código dessa sua classe e do código da sua lista.

1 curtida

O que tem de pesado nesses 3000 objetos pra ocupar tanta RAM? E isso vem de qual fonte de dados?

1 curtida

Chegou a testar só com um elemento? Com a lista vazia? Sem a lista, etc? Mudou algo?


Na verdade, primeiro tenho outra dúvida: como você mediu a memória sendo usada? Se foi abrindo o gerenciador de tarefas do Windows (ou os comandos ps/top do Linux) e vendo a memória do processo “java”, essa não é a melhor maneira. Pois assim você só vê o total que a JVM está usando, e não a memória da sua aplicação.

Quando a JVM inicia, ela já aloca uma porção de memória para várias estruturas internas, além da própria aplicação. Então mesmo um programa simples vai precisar de bastante memória (proporcionalmente falando).

E quando o GC “limpa” um objeto, não necessariamente a memória que este estava usando é devolvida ao sistema operacional. A JVM pode manter aquela porção de memória alocada para reusar futuramente.

Se quer saber quanto a sua aplicação está usando de memória, tem que usar ferramentas específicas. Exemplos: https://www.baeldung.com/java-profilers

2 curtidas

Opaa, obg pela resposta, eu não posso passar esse código aqui, mas esses 3000 objetos se tratam de objetos de um game, tem variáveis de posições, vida, id etc… por isso eu precisava instanciar no máximo objetos que estivessem na visão do player, por isso preciso limpar a memória antes de fazer isso

São objetos com algumas variáveis e validações que precisei fazer, eu não posso instanciar tantos pois realmente é um pouco pesada a classe rs…

Sim, testei com a lista vazia, são ocupados 400-500mb.
Essa memória que a JVM mantém alocada, quando precisar sair para alocar memória para o que vem a ser alocado depois, sairá normalmente? pois o que eu queria realmente dando clear no ArrayList era que o gc identificasse que não existe mais aquele objeto e tirasse da memória que é o que ele não faz.

Seu player vai enxergar 3 mil objetos simultaneamente?

Quais são os atributos “pesados” desses objetos?

Eles armazenam imagens? Sons?
Se for o caso, cogite implementar o padrão Flyweight pra economizar recursos.

2 curtidas

Game? Esquece Java entao, maioria dos jogos profissionais sao feitos em C++.

E uma forma mais fácil seria usar Unity com C#.

Novamente eu pergunto: como você mediu? Se você só viu a memória total do processo “java”, está apenas vendo o que a JVM está usando no total. Se quer ver o que a sua aplicação está usando, tem que usar um profiler (veja o link que coloquei na mensagem anterior).

Um pequeno exemplo com o jvisualvm (que já vem instalado com o Java, pelo menos até o JDK 8, mas enfim, é só para dar um exemplo).

Fiz uma classe simples:

import java.util.Random;

public class Dados {
    private static Random rand = new Random();
    private int valor;
    public Dados() {
        this.valor = rand.nextInt();
    }
}

E um teste simples que adiciona elementos na lista, ou limpa ela:

import java.util.*;

public class ProfilerTest {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        List<Dados> lista = new ArrayList<>();
        while (true) {
            System.out.println("1 - inserir\n2 - limpar\n Digite qualquer outra coisa para sair");
            int opcao = Integer.parseInt(sc.nextLine());
            if (opcao == 1) {
                System.out.println("quantidade:");
                int qtd = Integer.parseInt(sc.nextLine());
                for (int i = 0; i < qtd; i++) {
                    lista.add(new Dados());
                }
            } else if (opcao == 2) {
                lista.clear();
            } else break;
        }
    }
}

Iniciei o jvisualvm, e rodei o programa acima com java ProfilerTest. Ou seja, uma JVM apenas com esse programa. Não vou explicar como usa o jvisualvm (para isso tem tutoriais por aí), só vou mostrar os resultados. Logo que iniciei o programa, apareceu o seguinte:

O heap está ocupando cerca de 200MB, e o “used heap”, cerca de 14MB. Sem entrar em muitos detalhes, o heap é onde ficam os objetos que você cria, e a JVM já aloca uma quantidade de memória para ele (no caso, os 200MB). Mas o meu programa só está usando de fato 14MB (na verdade não é só o meu programa, leia mais a respeito aqui).

Depois eu criei 1000 instâncias da classe Dados e adicionei na lista (ou seja, escolhi a opção “1” e depois digitei “1000” para a quantidade). No jvisualvm eu consigo ver que as instâncias foram criadas:

heap2

Não vou colocar todos os gráficos, mas o heap continuou ocupando 200MB, e o used heap subiu um pouco (para uns 20MB).

Depois adicionei 100 milhões de instâncias, o heap passou a ocupar 2,8GB e o used heap foi para 2,3GB.

Aí eu limpei a lista, e a memória não é imediatamente liberada. Na verdade ficou alguns minutos ocupando os mais de 2GB. Só depois de um tempo ela diminuiu (provavelmente porque o GC rodou), mas o heap não voltou aos 200MB originais, e sim para 1,4GB (e o used heap para cerca de 400MB). Lembrando que esse comportamento pode não ser o mesmo dependendo da versão da JVM, das configurações que você usou, etc. O fato é que não é garantido que ela vai voltar para o valor original (200MB), porque, sei lá, ela pode achar que como o programa de repente usou mais de 2GB, ele pode voltar a usar, então é melhor não devolver muita memória (apenas especulação, pode ser que seja por outro motivo, e pode ser que dependendo da versão da JVM o algoritmo seja outro). Enfim, é só para mostrar que não é algo assim tão determinístico, é algo que você não controla.

Claro que se você testar, os valores podem mudar, pois depende de vários fatores (um deles é o que o código faz - no meu caso foi simples, mas dependendo do código, se os objetos da lista estiverem sendo referenciados de outros pontos da aplicação, por exemplo, eles não são liberados). E é possível configurar o tamanho do heap (limites máximo e mínimo), entre outros: por exemplo, fiz outro teste com java -Xms500m -Xmx1g ProfilerTest (mínimo 500MB e máximo 1GB), e o heap nunca fica abaixo de 500MB (mesmo depois do GC rodar).


Enfim, você não controla quando o GC roda, ele vai rodar quando achar melhor. É o preço de não precisar se preocupar com gerenciamento de memória, você abre mão desse controle.

Se realmente não estiver liberando memória, pode ser que ainda haja alguma referência para esses objetos em outro ponto do programa. Ou o GC ainda não decidiu rodar, ou rodou mas decidiu não devolver toda a memória para o sistema operacional, para que seja reusada depois, etc.

Se precisa que a memória seja liberada imediatamente, então na verdade seu problema é outro: ou você está carregando mais coisas do que precisa, ou o programa de fato precisa de mais memória.

Então você tem avaliar se realmente precisa desses 3000 objetos de uma vez, se não dá para diminuir ou otimizar a carga desses, etc (e sem ver o código, é o máximo que dá para sugerir). Se depois de avaliar, você concluir que não tem jeito e tem que usar esses 3000 objetos, então vai ter que ajustar a JVM para usar a memória necessária: https://docs.oracle.com/cd/E13222_01/wls/docs81/perform/JVMTuning.html

2 curtidas

Legal, ótima explicação, vou fazer o teste, realmente é um preço que se paga por não conseguir manipular a memória como eu quero, vou terminar esse projeto em java já que está na metade e os próximos vou ver se mudo para outra linguagem quando for fazer um sistema desse tamanho, vou fazer o teste com o profiler e ver o que da para fazer para diminuir de memória