Combate JVM: mecanismo de retorno de memória física CMS e G1

Prefácio

A empresa possui um sistema que utiliza o coletor de lixo CMS. A memória heap inicial da JVM não é igual à memória heap máxima. Porém, através do monitoramento das informações, verifica-se que após um FullGC, o espaço restante da memória física do servidor não aumentou. De acordo com meu entendimento anterior após FullGC Parte da memória liberada pelo processo JVM é retornada à memória física. Vamos comparar e verificar o mecanismo de retorno da memória física de CMS e G1 por meio de alguns experimentos.

Código de teste

public class MemoryRecycleTest {
    
    

    static volatile List<OOMobject> list = new ArrayList<>();

    public static void main(String[] args) {
    
    
        //指定要生产的对象大小为512M
        int count = 512;

        //新建一条线程,负责生产对象
        new Thread(() -> {
    
    
            try {
    
    
                for (int i = 1; i <= 10; i++) {
    
    
                    System.out.println(String.format("第%s次生产%s大小的对象", i, count));
                    addObject(list, count);
                    //休眠40秒
                    Thread.sleep(i * 10000);
                }
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }).start();

        //新建一条线程,负责清理List,回收JVM内存
        new Thread(() -> {
    
    
            for (; ; ) {
    
    
                //当List内存到达512M,就通知GC回收堆
                if (list.size() >= count) {
    
    
                    System.out.println("清理list.... 回收jvm内存....");
                    list.clear();
                    //通知GC回收
                    System.gc();
                    //打印堆内存信息
                    printJvmMemoryInfo();
                }
            }
        }).start();

        //阻止程序退出
        try {
    
    
            Thread.currentThread().join();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }

    public static void addObject(List<OOMobject> list, int count) {
    
    
        for (int i = 0; i < count; i++) {
    
    
            OOMobject ooMobject = new OOMobject();
            //向List添加一个1M的对象
            list.add(ooMobject);
            try {
    
    
                //休眠100毫秒
                Thread.sleep(100);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }

    public static class OOMobject {
    
    
        //生成1M的对象
        private byte[] bytes = new byte[1024 * 1024];
    }

    public static void printJvmMemoryInfo() {
    
    
        //虚拟机级内存情况查询
        long vmFree = 0;
        long vmUse = 0;
        long vmTotal = 0;
        long vmMax = 0;
        int byteToMb = 1024 * 1024;
        Runtime rt = Runtime.getRuntime();
        vmTotal = rt.totalMemory() / byteToMb;
        vmFree = rt.freeMemory() / byteToMb;
        vmMax = rt.maxMemory() / byteToMb;
        vmUse = vmTotal - vmFree;
        System.out.println("");
        System.out.println("JVM内存已用的空间为:" + vmUse + " MB");
        System.out.println("JVM内存的空闲空间为:" + vmFree + " MB");
        System.out.println("JVM总内存空间为:" + vmTotal + " MB");
        System.out.println("JVM总内存最大堆空间为:" + vmMax + " MB");
        System.out.println("");
    }
}

JDK8 CMS

Parâmetros JVM :

-Xms128M -Xmx2048M -XX:+UseConcMarkSweepGC

O que o console imprime :

第1次生产512大小的对象
清理list.... 回收jvm内存....

JVM内存已用的空间为:6 MB
JVM内存的空闲空间为:1202 MB
JVM总内存空间为:1208 MB
JVM总内存最大堆空间为:1979 MB

第2次生产512大小的对象
清理list.... 回收jvm内存....

JVM内存已用的空间为:3 MB
JVM内存的空闲空间为:1097 MB
JVM总内存空间为:1100 MB
JVM总内存最大堆空间为:1979 MB

第3次生产512大小的对象
清理list.... 回收jvm内存....

JVM内存已用的空间为:3 MB
JVM内存的空闲空间为:706 MB
JVM总内存空间为:709 MB
JVM总内存最大堆空间为:1979 MB

第4次生产512大小的对象
清理list.... 回收jvm内存....

JVM内存已用的空间为:3 MB
JVM内存的空闲空间为:120 MB
JVM总内存空间为:123 MB
JVM总内存最大堆空间为:1979 MB

Memória heap monitorada pelo VisualVM :

Insira a descrição da imagem aqui

Pode ser visto a partir da situação da memória heap na figura que sob a configuração de JDK8 + CMS, JVM não retorna a memória para o sistema operacional imediatamente, mas a retorna gradualmente conforme o número de FullGC aumenta e, eventualmente, retornará tudo isso

JDK8 G1

Parâmetros JVM :

-Xms128M -Xmx2048M -XX:+UseG1GC

Memória heap monitorada pelo VisualVM :

Insira a descrição da imagem aqui

Na configuração de JDK8 + G1, JVM retorna toda a memória física após cada FullGC

JDK11 CMS

Parâmetros JVM :

-Xms128M -Xmx2048M -XX:+UseConcMarkSweepGC

Memória heap monitorada pelo VisualVM :

Insira a descrição da imagem aqui

Na configuração do JDK11 + CMS, a situação é a mesma do JDK8 + CMS (JVM não retorna a memória para o sistema operacional imediatamente, mas a retorna gradualmente conforme o número de FullGC aumenta e, eventualmente, retornará tudo)

JDK11 fornece um parâmetro JVM ShrinkHeapInSteps. Com este parâmetro, a memória pode ser devolvida ao sistema operacional gradualmente após o GC. No JDK11, este parâmetro é habilitado por padrão. Você pode desativar este parâmetro para ver como a memória heap muda:

-Xms128M -Xmx2048M -XX:+UseConcMarkSweepGC -XX:-ShrinkHeapInSteps

Memória heap monitorada pelo VisualVM :

Insira a descrição da imagem aqui

Na configuração do ShrinkHeapInStepsJDK11 + CMS, após desligar os parâmetros, o JVM irá retornar toda a memória física após cada FullGC

JDK11 G1

Como o JDK11 usa o coletor de lixo G1 por padrão, apenas a memória heap inicial e a memória heap máxima são definidas aqui

Parâmetros JVM :

-Xms128M -Xmx2048M

Memória heap monitorada pelo VisualVM :

Insira a descrição da imagem aqui

1) JDK11 ShrinkHeapInStepsé habilitado por padrão por padrão, mas aqui, as mudanças na memória heap não são reduzidas gradualmente. Portanto, ShrinkHeapInStepsé inválido no coletor G1 . Se desligarmos manualmente os ShrinkHeapInStepsparâmetros, descobrimos que as mudanças na memória heap são semelhantes às anteriores

2) G1 sob JDK11 e G1 sob JDK8 têm respostas diferentes à memória. Do ponto de vista das mudanças na memória heap, G1 no JDK11 é mais inclinado a usar a memória tanto quanto possível e não apressar para reciclá-la . G1 no JDK8 tende a recuperar a memória tanto quanto possível. Pela figura, o tamanho real da memória heap de G1 em JDK8 é basicamente a metade de G1 em JDK11

resumo

Se o código permanecer o mesmo, mas as configurações de Xms e Xmx nos parâmetros JVM forem as mesmas, independentemente de haver FullGC, o tamanho da memória heap não será alterado e a memória não será liberada para o sistema operacional

Como devolver a memória ao sistema operacional após GC:

  • Se ele pode ser retornado depende se Xms e Xmx são iguais
  • Quando retornar, depende principalmente da versão do JDK e do tipo de coletor de lixo

Somente no FullGC a memória heap pode ser reduzida e retornada ao sistema operacional. YGC não pode fazer com que a JVM retorne ativamente a memória para o sistema operacional

Tente manter Xms e Xmx consistentes, de modo a reduzir a perda de desempenho causada pelo ajuste de memória heap e também reduzir o risco de falta de memória causado pelo ajuste de memória heap

referência:

https://segmentfault.com/a/1190000019856974

https://www.cnblogs.com/androidsuperman/p/11743103.html

http://blog.dutycode.com/archives/jvmjvm%E7%9A%84xms%E5%8F%82%E6%95%B0%E5%92%8Clinuxtop%E5%91%BD%E4%BB%A4% E7% 9A% 84res% E5% 85% B3% E7% B3% BB

Acho que você gosta

Origin blog.csdn.net/qq_40378034/article/details/110677269
Recomendado
Clasificación