Como forçar a coleta de lixo em Java?

225

É possível forçar a coleta de lixo em Java, mesmo que seja complicado? Eu sei sobre System.gc();e Runtime.gc();mas eles apenas sugerem fazer GC. Como posso forçar o GC?

Robert Columbia
fonte
30
Talvez seja útil fornecer algumas informações sobre por que você precisa forçar o GC. Normalmente, em um idioma de coleta de lixo, é uma prática ruim chamar explicitamente o coletor.
23630 Justin Ethier
3
Uma determinada JVM pode fornecer vários métodos de coleta de lixo, cada um com suas próprias vantagens e desvantagens, e freqüentemente uma determinada situação pode ser evitada simplesmente sugerindo a JVM no momento da inicialização. Por favor, elabore o cenário.
Thorbjørn Ravn Andersen
3
jmap -histo: live <pid> stackoverflow.com/questions/6418089/…
5
Aqui está um caso de uso para forçar a coleta de lixo: Eu tenho um servidor com um heap de 30 GB, dos quais ~ 12 GB normalmente são usados ​​(~ 5 milhões de objetos). A cada 5 minutos, o servidor gasta aproximadamente um minuto executando uma tarefa complexa na qual aproximadamente 35 milhões de objetos adicionais são usados. Um GC completo é acionado algumas vezes por hora, invariavelmente durante a tarefa complexa, e congela a VM por 10 a 15 segundos. Eu adoraria forçar a execução do GC completo em um momento em que a tarefa complexa não está sendo executada; seria então manipular 5 milhões de objetos ao vivo em vez de 40 milhões.
21813 Steve Steve
3
@JustinEthier Há um caso bastante óbvio em que você pode forçar o GC, que é o teste de unidade de qualquer comportamento que envolva a hierarquia de tipos java.lang.ref.Reference.
Elias Vasylenko 5/10

Respostas:

168

Sua melhor opção é chamar o System.gc()que simplesmente é uma dica para o coletor de lixo que você deseja que ele faça uma coleção. Não há como forçar e coletar imediatamente, pois o coletor de lixo não é determinístico.

Andrew Hare
fonte
28
Deve haver. non-deterministic == trouble
Pacerier 23/08/14
7
Um coletor de lixo pode não ser determinístico e ainda oferecer uma maneira de forçar uma coleta imediata. Por exemplo, geralmente o coletor .NET não é determinístico, mas uma chamada para GC.Collect () força a execução. É que o Java escolhe não expor essa função.
Petr Hudeček
2
Na minha experiência, esse método sempre chama o coletor de lixo. Faz isso com regularidade suficiente para que meus lotes de memória usados ​​versus número de objetos declarados sejam sempre estritamente lineares (considerando o preenchimento etc.).
Jim Pivarski
Eu estava pensando que através da atribuição de novos objetos e, em seguida, não referenciando-los mais, o coletor de lixo seria automaticamente executado
Bionix1441
@ PetrHudeček Em aplicativos do mundo real, o .NET GC.Collect()não coleta. Em Java gc()faz.
31418 Ajeh
53

A biblioteca jlibs possui uma boa classe de utilitário para coleta de lixo . Você pode forçar a coleta de lixo usando um truque bacana com objetos WeakReference .

RuntimeUtil.gc () dos jlibs:

   /**
    * This method guarantees that garbage collection is
    * done unlike <code>{@link System#gc()}</code>
    */
   public static void gc() {
     Object obj = new Object();
     WeakReference ref = new WeakReference<Object>(obj);
     obj = null;
     while(ref.get() != null) {
       System.gc();
     }
   }
shams
fonte
1
Esse código está quebrado porque uma referência fraca é limpa assim que seu referente se torna fracamente acessível, ou seja, antes de ser limpo da memória.
Marko Topolnik
1
Você pode estar confundindo o significado de "GC foi executado" com "a memória foi recuperada". O objeto continua vivo e nem está finalizado, mas você não pode mais acessá-lo através da referência fraca. Uma maneira um pouco melhor seria usar a PhantomReferencecom a ReferenceQueuee você seria notificado após a finalização, mas ainda antes da limpeza. Por fim, mesmo que você tenha detectado com êxito que a memória desse objeto foi recuperada, isso ainda significaria muito pouco em um GC geracional como o HotSpot. Geralmente coincidiria com a limpeza da geração jovem.
Marko Topolnik
20
O OP solicitou e você alegou fornecer uma solução para "forçar a coleta de lixo". A execução do subsistema GC é uma coisa, na verdade coletando lixo outra. O exemplo de código que você forneceu tem claramente a intenção de garantir a coleta de lixo. De qualquer forma, essa sendo uma pergunta muito antiga, obviamente não se trata dos desejos da OP, mas de utilidade para o público em geral. Ninguém está interessado em "forçar o subsistema GC a rodar" por conta própria, sem coleta de lixo. De fato, as pessoas geralmente querem uma garantia de que todo o lixo foi coletado.
Marko Topolnik
4
Você provavelmente não o comparou com a eficiência de System.gc(); System.gc();, mas com certeza seria interessante saber se funcionou melhor do que isso. De fato, apenas imprimir quantas vezes ele chamou System.gc()seria suficiente. A chance de chegar a 2 é bastante pequena.
Marko Topolnik
3
@ MarkoTopolnik: 'Ninguém está interessado em "forçar o subsistema GC a funcionar" por conta própria, sem coleta de lixo' ... Na verdade, eu estava interessado apenas nesse comportamento hoje. Sou grato por esta resposta estar presente. Meu objetivo era verificar o comportamento rotacional da manipulação de logs do GC e obter o formato da saída do GC. Este pequeno truque me ajudou a preencher os logs do GC rapidamente.
Erik.weathers #
49

A melhor maneira (se não apenas) de forçar um GC seria escrever uma JVM customizada. Eu acredito que os coletores de lixo são conectáveis, então você provavelmente pode escolher uma das implementações disponíveis e ajustá-la.

Nota: Esta NÃO é uma resposta fácil.

Mainguy
fonte
40
+1 para lulz. nada torna a depuração frustrante melhor do que alguém com senso de humor. diferente de uma resposta realmente útil, ou seja.
jsh
25

SIM , é quase possível forçar você a chamar métodos na mesma ordem e, ao mesmo tempo, são:

System.gc ();
System.runFinalization ();

mesmo que seja apenas um objeto para limpar o uso desses dois métodos ao mesmo tempo, force o coletor de lixo a usar o finalise()método de objeto inacessível, liberando a memória atribuída e fazendo o que o finalize()método declara.

No entanto , é uma péssima prática usar o coletor de lixo porque o uso dele pode introduzir uma sobrecarga no software que pode ser ainda pior do que na memória, o coletor de lixo tem seu próprio encadeamento que não é possível controlar mais, dependendo da o algoritmo usado pelo gc pode levar mais tempo e é considerado muito ineficiente; você deve verificar o seu software se for pior com a ajuda do gc, pois está definitivamente quebrado, uma boa solução não deve depender do gc.

NOTA: para ter em mente, isso funcionará apenas se no método finalizar não houver uma reatribuição do objeto; se isso acontecer, o objeto permanecerá vivo e haverá uma ressurreição tecnicamente possível.

legramira
fonte
10
NÃO , mesmo esses dois comandos NÃO forçarão uma coleta de lixo. Como já mencionado por outros, gc()é apenas uma dica para executar uma coleta de lixo. runFinalizers()somente executa finalizadores em objetos "que foram encontrados como descartados". Se o gc não chegou a executar, pode haver nenhum desses objetos ...
Steffen Heil
Além disso, System.runFinalization () não é garantia de que algo será executado; é possível que nada aconteça. É uma sugestão - de Javadoc: " Chamar esse método sugere que a Java Virtual Machine dedique esforço para executar os métodos finalize de objetos que foram encontrados para serem descartados, mas cujos métodos finalize ainda não foram executados "
kaan
21

Sob a documentação de OutOfMemoryError , declara que não será lançada, a menos que a VM falhe ao recuperar a memória após uma coleta de lixo completa. Portanto, se você continuar alocando memória até obter o erro, já terá forçado uma coleta de lixo completa.

Presumivelmente, a pergunta que você realmente queria fazer era "como recuperar a memória que acho que deveria recuperar pela coleta de lixo?"

Pete Kirkham
fonte
18

Para solicitar manualmente o GC (não do System.gc ()):

  1. Vá para: pasta bin no JDK, por exemplo, -C: \ Arquivos de Programas \ Java \ jdk1.6.0_31 \ bin
  2. Abra o jconsole.exe
  3. Conecte-se ao processo local desejado.
  4. Vá para a guia Memória e clique em Executar GC.
Pinkesh Sharma
fonte
3
Oppsss enganosa. Passe o mouse sobre o botão "Executar GC". Você pode solicitar à JVM para executar o GC, mas nunca forçar.
Kumaran
@PinkeshSharma, isso não força . É um mero pedido que provavelmente poderia ser totalmente ignorado.
Pacerier 23/08/14
@Pacerier Em um mundo ideal, sim .. mas se você fizer isso você vai ver que há um aumento na memória instantaneamente ...
Pinkesh Sharma
11

.gc é candidato à eliminação em versões futuras - um engenheiro da Sun comentou uma vez que talvez menos de vinte pessoas no mundo realmente sabem como usar .gc () - eu trabalhei ontem à noite por algumas horas em uma central / crítica estrutura de dados usando dados gerados pelo SecureRandom, em algum lugar além dos 40.000 objetos, a vm diminuiria a velocidade como se tivesse ficado sem ponteiros. Claramente, ele estava afogado em tabelas de ponteiros de 16 bits e exibia o comportamento clássico de "máquinas com falha".

Eu tentei -Xms e assim por diante, continuei mexendo um pouco até chegar a 57, xxx alguma coisa. Depois, o gc passaria de digamos 57.127 para 57.128 depois de um gc () - mais ou menos no ritmo do inchaço do código no campo Easy Money.

Seu design precisa de um retrabalho fundamental, provavelmente uma abordagem de janela deslizante.

Nicholas Jordan
fonte
1
Eu tenho algo assim, muitos objetos na memória que não consigo desalocá-los. A exceção OutOfMemory é lançada. Quero forçar o GC a testar se existe algum processo de criação de Objetos Infinitos ou se esses objetos são os usados ​​pelo meu sistema.
Parece que você está trabalhando no mesmo problema que eu, pls explica: "Criação de objetos infinitos" ... bom projeto de pesquisa, talvez você possa postar uma pergunta ou algo em uma área java aqui (eu sou novo aqui e não o faço conheço o "Finite Automa" de como o site funciona) Eu tentei ontem e acabei processando file.dat, pois o compilador reclamou "muito código" em 40.000 bases36 BigIntegers codificados como uma final estática String [] Vou enfiar meu pescoço aqui e especular que toda a JVM é limitado em ponteiros de 16 bits, eu aposto que nós temos que fazer é agressivamente nula e ler a partir do disco ...
Nicholas Jordan
Realmente, eu não entendo você. Mas, para ser claro sobre a "Criação de Objetos Infinitos", eu quis dizer que há um pedaço de código no meu grande sistema que cria objetos que manipulam e estão vivos na memória.
5
Absurdo! Há um caso óbvio em que ele deve ser usado: testar código que usa referências fracas, para que possamos garantir que o comportamento seja correto quando as referências fracas forem esclarecidas.
Elias Vasylenko
6

A especificação da JVM não diz nada específico sobre coleta de lixo. Devido a isso, os fornecedores são livres para implementar o GC à sua maneira.

Portanto, essa imprecisão causa incerteza no comportamento da coleta de lixo. Você deve verificar os detalhes da JVM para conhecer as abordagens / algoritmos de coleta de lixo. Também há opções para personalizar o comportamento também.

rai.skumar
fonte
4

Se você precisar forçar a coleta de lixo, talvez deva considerar como está gerenciando recursos. Você está criando objetos grandes que persistem na memória? Você está criando objetos grandes (por exemplo, classes de gráficos) que possuem uma Disposableinterface e não chamam dispose()quando terminam? Você está declarando algo em um nível de classe que você só precisa em um único método?

Bob Kaufman
fonte
2

Seria melhor se você descrevesse o motivo pelo qual você precisa de coleta de lixo. Se você estiver usando SWT, poderá dispor de recursos como Imagee Fontpara liberar memória. Por exemplo:

Image img = new Image(Display.getDefault(), 16, 16);
img.dispose();

Existem também ferramentas para determinar recursos não dispostos.

Paul Lammertsma
fonte
e se não houver nenhum método de descarte?
ArifMustafa 6/09/16
1
Completamente sem relação com a pergunta! Não, não estou usando SWT. Estou chamando um método JNI que abre uma janela .NET através de uma camada nativa Delphi. Eu também tenho um núcleo de computação FORTRAN que recebe dados através de uma camada C ++ nativa. O que isso tem a ver com alguma coisa? Posso forçar um GC ou não? Não? :-(
Mostafa Zeinali
0

Se você está ficando sem memória e obtendo um, OutOfMemoryExceptionpode tentar aumentar a quantidade de espaço de heap disponível para java iniciando o programa em java -Xms128m -Xmx512mvez de apenas java. Isso fornecerá um tamanho de pilha inicial de 128Mb e um máximo de 512Mb, que é muito mais do que os 32Mb / 128Mb padrão.

Viktor Dahl
fonte
As configurações de memória padrão sãojava -Xms512M -Xmx1024M
ThePyroEagle
0

Outra opção é não criar novos objetos.

O pool de objetos está ausente para reduzir a necessidade de GC em Java.

O pool de objetos geralmente não será mais rápido que a criação de objetos (especialmente para objetos leves), mas é mais rápido que a coleta de lixo. Se você criou 10.000 objetos e cada objeto tinha 16 bytes. São 160.000 bytes que o GC precisa recuperar. Por outro lado, se você não precisar de todos os 10.000 ao mesmo tempo, poderá criar um pool para reciclar / reutilizar os objetos, o que elimina a necessidade de construir novos objetos e elimina a necessidade de objetos antigos do GC.

Algo assim (não testado). E se você deseja que ele seja seguro para threads, você pode trocar o LinkedList por um ConcurrentLinkedQueue.

public abstract class Pool<T> {
    private int mApproximateSize;
    private LinkedList<T> mPool = new LinkedList<>();

    public Pool(int approximateSize) {
        mApproximateSize = approximateSize;
    }

    public T attain() {
        T item = mPool.poll();
        if (item == null) {
            item = newInstance();
        }
        return item;
    }

    public void release(T item) {
        int approxSize = mPool.size(); // not guaranteed accurate
        if (approxSize < mApproximateSize) {
            recycle(item);
            mPool.add(item);
        } else if (approxSize > mApproximateSize) {
            decommission(mPool.poll());
        }
    }

    public abstract T newInstance();

    public abstract void recycle(T item);

    public void decommission(T item) { }

}
Aaron T Harris
fonte
0

No OracleJDK 10 com G1 GC, uma única chamada System.gc()fará com que o GC limpe a coleção antiga. Não tenho certeza se o GC é executado imediatamente. No entanto, o GC não limpará a coleção Young, mesmo que System.gc()seja chamado várias vezes em um loop. Para que o GC limpe a Coleção Jovem, você deve alocar em um loop (por exemplo new byte[1024]) sem chamar System.gc(). Ligar System.gc()por algum motivo impede que o GC limpe a Coleção Young.

Nathan
fonte
0

Realmente, eu não entendo você. Mas, para ser claro sobre a "Criação de Objetos Infinitos", eu quis dizer que há um pedaço de código no meu grande sistema que cria objetos que manipulam e estão vivos na memória.

Isso está correto, apenas gesto. Você tem praticamente as respostas padrão já dadas por vários pôsteres. Vamos pegar um por um:

  1. Na verdade, não consegui pegar esse pedaço de código

Correto, não existe jvm real - isso é apenas uma especificação, um monte de ciência da computação descrevendo o comportamento desejado ... Recentemente, procurei inicializar objetos Java a partir de código nativo. Para conseguir o que deseja, a única maneira é fazer o que é chamado de nulo agressivo. Os erros cometidos de maneira errada são tão ruins que precisamos nos limitar ao escopo original da pergunta:

  1. algum pedaço de código no meu grande sistema faz a criação de objetos

A maioria dos pôsteres aqui assumirá que você está dizendo que está trabalhando em uma interface, caso seja necessário, teremos que verificar se você está recebendo o objeto inteiro ou um item de cada vez.

Se você não precisar mais de um objeto, poderá atribuir nulo ao objeto, mas se errar, será gerada uma exceção de ponteiro nulo. Aposto que você pode conseguir um trabalho melhor se usar o NIO

Sempre que você ou eu ou qualquer outra pessoa obtém: " Por favor, preciso disso horrivelmente. ", É quase um precursor quase universal da destruição quase total do que você está tentando trabalhar ... escreva-nos um pequeno código de exemplo, higienizando-o código real usado e nos mostre sua pergunta.

Não fique frustrado. Muitas vezes, o que isso resolve é que o seu dba está usando um pacote comprado em algum lugar e o design original não é aprimorado para estruturas de dados maciças.

Isso é muito comum.

Nicholas Jordan
fonte
-1

Para sua informação

A chamada de método System.runFinalizersOnExit (true) garante que os métodos do finalizador sejam chamados antes que o Java seja encerrado. No entanto, esse método é inerentemente inseguro e foi preterido. Uma alternativa é adicionar "ganchos de desligamento" com o método Runtime.addShutdownHook.

Masarrat Siddiqui

Masarrat Siddiqui
fonte
A falha nos ganchos de desligamento é que eles raramente funcionam. O encerramento forçado não funciona, o código de saída diferente de zero não funciona e, às vezes, a JVM (oficial) simplesmente não os executa contanto que você precise executar.
ThePyroEagle
-1

Existe alguma maneira indireta de forçar o coletor de lixo. Você só precisa preencher a pilha com objetos temporários até o momento em que o coletor de lixo será executado. Eu criei uma classe que força o coletor de lixo desta maneira:

class GarbageCollectorManager {

    private static boolean collectionWasForced;
    private static int refCounter = 0;

    public GarbageCollectorManager() {
        refCounter++;
    }

    @Override
    protected void finalize() {
        try {
            collectionWasForced = true;
            refCounter--;
            super.finalize();   
        } catch (Throwable ex) {
            Logger.getLogger(GarbageCollectorManager.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public int forceGarbageCollection() {
        final int TEMPORARY_ARRAY_SIZE_FOR_GC = 200_000;
        int iterationsUntilCollected = 0;
        collectionWasForced = false;

        if (refCounter < 2) 
            new GarbageCollectorManager();

        while (!collectionWasForced) {
            iterationsUntilCollected++;
            int[] arr = new int[TEMPORARY_ARRAY_SIZE_FOR_GC];
            arr = null;
        }

        return iterationsUntilCollected;
    }

}

Uso:

GarbageCollectorManager manager = new GarbageCollectorManager();
int iterationsUntilGcExecuted = manager.forceGarbageCollection();

Eu não sei o quanto esse método é útil, porque ele preenche a pilha constantemente, mas se você tiver um aplicativo de missão crítica que DEVE forçar o GC - quando essa pode ser a maneira portátil em Java de forçar o GC.

Agnius Vasiliauskas
fonte
O que é "int int TEMPORARY_ARRAY_SIZE_FOR_GC = 200_000;"?
Koray Tugay
Tamanho da matriz - quantos objetos temporários (int's) serão gerados para que o GC comece a funcionar.
Agnius Vasiliauskas
Isso é válido: "_" em um número inteiro?
Koray Tugay
1
Sim, sublinhados em literais numéricos são válidos a partir do Java SE 7. Isso é útil, por exemplo, como separador de milhares em número inteiro, como neste caso.
Agnius Vasiliauskas
3
Você nunca deve executar esse código em um sistema de produção. Embora esse código é executado em um segmento qualquer outro segmento também pode obter um OutOfMemoryException, reversão completamente a intenção de chamar isso em primeiro lugar ....
Steffen Heil
-1

Eu gostaria de adicionar alguma coisa aqui. Por favor, não que o Java seja executado na Máquina Virtual e não na Máquina real. A máquina virtual possui seu próprio meio de comunicação com a máquina. Pode variar de sistema para sistema. Agora, quando chamamos o GC, pedimos à Máquina Virtual de Java que chame o Garbage Collector.

Como o Garbage Collector está com a Máquina Virtual, não podemos forçá-lo a fazer uma limpeza lá e depois. Em vez disso, enfileiramos nossa solicitação com o Garbage Collector. Depende da Máquina Virtual, após um determinado período de tempo (isso pode mudar de sistema para sistema, geralmente quando a memória de limite alocada para a JVM estiver cheia), a máquina real liberará o espaço. : D

Saubhagya Ranjan Das
fonte
A primeira frase do segundo parágrafo é um non sequitur .
Marquês de Lorne
-1

O código a seguir é retirado do método assertGC (...). Ele tenta forçar o coletor de lixo não determinístico a coletar.

   List<byte[]> alloc = new ArrayList<byte[]>();
   int size = 100000;
   for (int i = 0; i < 50; i++) {
        if (ref.get() == null) {
             // Test succeeded! Week referenced object has been cleared by gc.
             return;
        }
        try {
             System.gc();
        } catch (OutOfMemoryError error) {
             // OK
        }
        try {
             System.runFinalization();
        } catch (OutOfMemoryError error) {
             // OK
        }

        // Approach the jvm maximal allocatable memory threshold
        try {
             // Allocates memory.
             alloc.add(new byte[size]);

             // The amount of allocated memory is increased for the next iteration.
             size = (int)(((double)size) * 1.3);
        } catch (OutOfMemoryError error) {
             // The amount of allocated memory is decreased for the next iteration.
             size = size / 2;
        }

        try {
             if (i % 3 == 0) Thread.sleep(321);
        } catch (InterruptedException t) {
             // ignore
        }
   }

   // Test failed! 

   // Free resources required for testing
   alloc = null;

   // Try to find out who holds the reference.
   String str = null;
   try {
        str = findRefsFromRoot(ref.get(), rootsHint);
   } catch (Exception e) {
        throw new AssertionFailedErrorException(e);
   } catch (OutOfMemoryError err) {
        // OK
   }
   fail(text + ":\n" + str);

Fonte (adicionei alguns comentários para maior clareza): Exemplo NbTestCase

H. Hess
fonte
-1

Você pode tentar usar Runtime.getRuntime().gc()ou usar o método utilitário System.gc()Nota: Esses métodos não garantem o GC. E seu escopo deve ser limitado à JVM em vez de manipulá-lo programaticamente em seu aplicativo.

nabster
fonte
2
Conforme explicado nas outras respostas, esses métodos não forçam uma execução de coleta de lixo (completa).
Flow
-2

Se você estiver usando JUnit e Spring, tente adicionar isso em todas as classes de teste:

@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
Aliuk
fonte