JVM 전투 : CMS 및 G1 물리적 메모리 반환 메커니즘

머리말

회사는 CMS 가비지 컬렉터를 사용하는 시스템을 보유하고 있습니다. JVM의 초기 힙 메모리는 최대 힙 메모리와 같지 않습니다. 그러나 모니터링 정보를 통해 FullGC 이후 서버의 물리적 메모리의 남은 공간이 있음을 알 수 있습니다. FullGC 이후 제가 이해 한 바에 따르면 JVM 프로세스에서 해제 한 메모리의 일부가 실제 메모리로 반환됩니다. 몇 가지 실험을 통해 CMS와 G1의 실제 메모리 반환 메커니즘을 비교하고 검증 해 보겠습니다.

테스트 코드

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

JVM 매개 변수 :

-Xms128M -Xmx2048M -XX:+UseConcMarkSweepGC

콘솔이 인쇄하는 내용 :

第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

VisualVM에서 모니터링하는 힙 메모리 :

여기에 사진 설명 삽입

그림에서 힙 메모리 상황에서 JDK8 + CMS 구성에서 JVM이 즉시 운영 체제로 메모리를 반환하지 않고 FullGC 수가 증가함에 따라 점차적으로 반환하고 결국에는 반환되는 것을 알 수 있습니다. 모든

JDK8 G1

JVM 매개 변수 :

-Xms128M -Xmx2048M -XX:+UseG1GC

VisualVM에서 모니터링하는 힙 메모리 :

여기에 사진 설명 삽입

JDK8 + G1 구성에서 JVM은 각 FullGC 이후에 모든 물리적 메모리를 반환합니다.

JDK11 CMS

JVM 매개 변수 :

-Xms128M -Xmx2048M -XX:+UseConcMarkSweepGC

VisualVM에서 모니터링하는 힙 메모리 :

여기에 사진 설명 삽입

JDK11 + CMS 구성에서 상황은 JDK8 + CMS와 동일합니다 (JVM은 메모리를 운영 체제로 즉시 반환하지 않지만 FullGC 수가 증가함에 따라 점차적으로 반환하고 결국에는 모두 반환합니다)

JDK11은 JVM 매개 변수를 제공합니다 ShrinkHeapInSteps. 이 매개 변수를 사용하면 GC 이후에 점진적으로 메모리를 운영 체제로 되돌릴 수 있습니다. JDK11에서이 매개 변수는 기본적으로 활성화됩니다. 이 매개 변수를 끄면 힙 메모리가 어떻게 변경되는지 확인할 수 있습니다.

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

VisualVM에서 모니터링하는 힙 메모리 :

여기에 사진 설명 삽입

ShrinkHeapInStepsJDK11 + CMS의 구성에서 매개 변수를 후 JVM은 각 FullGC 이후 모든 물리적 메모리를 반환합니다.

JDK11 G1

JDK11은 기본적으로 G1 가비지 수집기를 사용하므로 초기 힙 메모리와 최대 힙 메모리 만 여기에 설정됩니다.

JVM 매개 변수 :

-Xms128M -Xmx2048M

VisualVM에서 모니터링하는 힙 메모리 :

여기에 사진 설명 삽입

1) JDK11 ShrinkHeapInSteps기본적으로 기본적으로 활성화되어 있지만 여기서는 힙 메모리 변경 사항이 점진적으로 줄어들지 않습니다. 따라서 G1 수집기에서는 ShrinkHeapInSteps유효하지 않습니다. ShrinkHeapInSteps매개 변수를 수동으로 끄면 힙 메모리 변경이 위와 비슷하다는 것을 알 수 있습니다.

2) JDK11의 G1과 JDK8의 G1은 메모리에 대한 응답이 다릅니다. 힙 메모리 변경의 관점 에서 JDK11 아래의 G1은 메모리를 최대한 많이 사용하고 재순환하지 않는 경향이 있습니다 . JDK8의 G1은 가능한 한 메모리를 회수하는 경향이 있습니다. 그림에서 JDK8에서 G1의 실제 힙 메모리 크기는 기본적으로 JDK11에서 G1의 절반입니다.

요약

코드는 동일하지만 JVM 매개 변수의 Xms 및 Xmx 설정이 같으면 FullGC가 있는지 여부에 관계없이 힙 메모리 크기가 변경되지 않고 메모리가 운영 체제로 해제되지 않습니다.

GC 후 메모리를 운영 체제로 되 돌리는 방법 :

  • 반환 가능 여부는 Xms와 Xmx가 같은지 여부에 따라 다릅니다.
  • 반환시기는 주로 JDK 버전과 가비지 수집기 유형에 따라 다릅니다.

FullGC에서만 힙 메모리가 축소되고 OS로 복귀가 트리거 될 수 있습니다. YGC는 JVM이 운영 체제에 메모리를 적극적으로 반환하도록 할 수 없습니다.

힙 메모리 조정으로 인한 성능 손실을 줄이고 힙 메모리 조정으로 인한 메모리 없음 위험을 줄이기 위해 Xms 및 Xmx를 일관되게 유지하십시오.

참고:

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

추천

출처blog.csdn.net/qq_40378034/article/details/110677269