Guia para System.gc ()

Guia para System.gc ()

1. Visão geral

Neste tutorial, vamos investigar o métodoSystem.gc() localizado no pacotejava.lang.

Chamar explicitamenteSystem.gc() é conhecido por ser uma prática ruim. Vamos tentar entender por que e se há casos de uso em que chamar esse método pode ser útil.

2. Coleta de lixo

A Java Virtual Machine decide executar a coleta de lixo quando houver indicações para isso. Essas indicações diferem de umGC implementation para outro. Eles são baseados em diferentes heurísticas. No entanto, existem alguns momentos em que o GC será executado com certeza:

  • A nova geração (espaço garantido) está cheia, o que desencadeia pequenos GC

  • A geração antiga (espaços Eden + Survivor0 + Survivor1) está cheia, o que aciona o GC principal / completo

A única coisa que é independente da implementação do GC é a elegibilidade do objeto para a coleta de lixo.

Agora, vamos dar uma olhada no próprio métodoSystem.gc().

3. System.gc()

Uma invocação do método é simples:

System.gc()

OOracle documentation oficial afirma que:

Chamando o métodogcsuggests que a Java Virtual Machine despende esforços para reciclar objetos não usados, a fim de disponibilizar a memória que eles ocupam atualmente para reutilização rápida.

There is no guarantee that the actual GC will be triggered.

System.gc()triggers a major GC. Portanto, existe o risco de passar algum tempo na fase de parar o mundo, dependendo da implementação do coletor de lixo. Como resultado,we have an unreliable tool with a potentially significant performance penalty.

A existência de uma chamada explícita à coleta de lixo deve ser uma bandeira vermelha séria para todos.

Podemos evitar queSystem.gc() faça qualquer trabalho usando o sinalizador JVM-XX:DisableExplicitGC.

3.1. Ajuste de desempenho

É importante notar que antes de lançar umOutOfMemoryError,, a JVM executará um GC completo. Portanto, uma chamada explícita paraSystem.gc() will not save us from failure.

Garbage collectors nowadays are really smart. Eles têm todo o conhecimento sobre o uso de memória e outras estatísticas para poderem tomar decisões adequadas. Por isso, devemos confiar neles.

No caso de problemas de memória, temos umbunch of settings que podemos alterar para ajustar nosso aplicativo - começando com a escolha de um coletor de lixo diferente, definindo a proporção de tempo de aplicativo / tempo GC desejada e, finalmente, terminando com a definição de tamanhos fixos para a memória segmentos.

Também existem maneiras demitigate the effects of Full GC caused by an explicit call. Podemos usar uma das bandeiras:

-XX:+ExplicitGCInvokesConcurrent

or:

-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses

Se realmente quisermos que nosso aplicativo funcione corretamente, devemos resolver o problema real de memória subjacente.

No próximo capítulo, veremos um exemplo prático quando chamar explicitamenteSystem.gc() parece ser útil.

4. Exemplo de uso

4.1. Cenário

Vamos escrever um aplicativo de teste. We want to find a situation when calling System.gc() might be useful.

A coleta de lixo menor acontece com mais frequência do que a principal. Então, provavelmente devemos nos concentrar no último. Um único objeto é movido para o espaço de posse, se "sobreviveu" a algumas coleções e ainda é acessível a partir das raízes do GC.

Vamos imaginar que temos uma enorme coleção de objetos que estão vivos há algum tempo. Então, em algum ponto, estamos limpando a coleção de objetos. Talvez seja um bom momento para executarSystem.gc()?

4.2. Aplicação de demonstração

Criaremos um aplicativo de console simples que nos permitirá simular esse cenário:

public class DemoApplication {

    private static final Map cache = new HashMap();

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        while (scanner.hasNext()) {
            final String next = scanner.next();
            if ("fill".equals(next)) {
                for (int i = 0; i < 1000000; i++) {
                    cache.put(randomUUID().toString(), randomUUID().toString());
                }
            } else if ("invalidate".equals(next)) {
                cache.clear();
            } else if ("gc".equals(next)) {
                System.gc();
            } else if ("exit".equals(next)) {
                System.exit(0);
            } else {
                System.out.println("unknown");
            }
        }
    }
}

4.3. Executando a demonstração

Vamos executar nosso aplicativo com alguns sinalizadores adicionais:

-XX:+PrintGCDetails -Xloggc:gclog.log -Xms100M -Xmx500M -XX:+UseConcMarkSweepGC

Os dois primeiros sinalizadores são necessários para registrar as informações do GC. Os próximos dois sinalizadores estão definindo o tamanho inicial do heap e, em seguida, o tamanho máximo do heap. Queremos manter o tamanho do heap baixo para forçar o GC a ser mais ativo. Finalmente, estamos decidindo usar o CMS - coletor de lixo de marcação e varredura simultânea. É hora de executar nosso aplicativo!

Primeiro, vamostry to fill tenured space. Digitefill.

Podemosinvestigate our gclog.log arquivo para ver o que aconteceu. Veremos cerca de 15 coleções. A linha registrada para coleções únicas se parece com:

197.057: [GC (Allocation Failure) 197.057: [ParNew: 67498K->40K(75840K), 0.0016945 secs]
  168754K->101295K(244192K), 0.0017865 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] secs]

Como podemos ver, a memória está cheia.

Em seguida, vamosforce System.gc() digitandogc. Podemos ver que o uso de memória não mudou significativamente:

238.810: [Full GC (System.gc()) 238.810: [CMS: 101255K->101231K(168352K); 0.2634318 secs]
  120693K->101231K(244192K), [Metaspace: 32186K->32186K(1079296K)], 0.2635908 secs]
  [Times: user=0.27 sys=0.00, real=0.26 secs]

Depois de mais algumas execuções, veremos que o tamanho da memória permanece no mesmo nível.

Vamosclear the cache digitandoinvalidate. Não devemos ver mais linhas de log no arquivogclog.log.

Podemos tentar preencher o cache mais algumas vezes, mas nenhum GC está acontecendo. Este é um momento em quewe can outsmart the garbage collector. Agora, depois de forçar GC, veremos uma linha como:

262.124: [Full GC (System.gc()) 262.124: [CMS: 101523K->14122K(169324K); 0.0975656 secs]
  103369K->14122K(245612K), [Metaspace: 32203K->32203K(1079296K)], 0.0977279 secs]
  [Times: user=0.10 sys=0.00, real=0.10 secs]

Libertamos uma quantidade impressionante de memória! But was it really necessary right now? O que aconteceu?

De acordo com este exemplo, chamarSystem.gc() parece tentador quando estamos liberando objetos grandes ou invalidando caches.

5. Outros usos

Existem muito poucos motivos pelos quais uma chamada explícita ao métodoSystem.gc() pode ser útil.

Uma possível razão écleaning memory after server startup - estamos iniciando um servidor ou aplicativo que faz muita preparação. Depois disso, há muitos objetos a serem finalizados. No entanto, a limpeza após tal preparação não deve ser nossa responsabilidade.

Outra émemory leak analysis— it é mais uma prática de depuração do que algo que gostaríamos de manter no código de produção. ChamarSystem.gc()e ver o espaço de heap ainda alto pode ser uma indicação dememory leak.

6. Sumário

Neste artigo, investigamos o métodoSystem.gc() e quando ele pode parecer útil.

We should never rely on it when it comes to the correctness of our app. GC na maioria dos casos é mais inteligente do que nós e, no caso de quaisquer problemas de memória, devemos considerar o ajuste da máquina virtual em vez de fazer uma chamada explícita.

Como de costume, o código usado neste artigo pode ser encontradoover on GitHub.