Настройка JVM Изучение механизма возврата физической памяти в CMS и G1

JVM

Предисловие:

В компании есть система статистики ассетов.Частота использования очень низкая,но скорость запросов при ее использовании должна быть быстрой.Поэтому я подумал сделать в памяти какие-то кеши.Если долго не используется, он будет сохраняться на диск, а мусор будет собираться и возвращаться.Физическая память предоставляется операционной системе, тем самым экономя ценные ресурсы для других бизнес-систем. Когда я хорошенько поработал с кешированием, то обнаружил острую проблему: после того, как программа освободила ресурсы и уведомила GC о необходимости высвобождения ресурсов, используемая память кучи уменьшилась, а свободная увеличилась, но системная память, занятая процесс не уменьшился. Я проверил много информации и пытался много раз, но ни один из них не решил проблему идеально. Я не осознавал этого, пока не увидел комментарий о сборщике мусора G1.

Далее через небольшое демо я покажу вам разницу между двумя сборщиками мусора для возврата физической памяти. Если что-то не так, надеюсь, вы исправите это в комментариях.

  • Конфигурация размера кучи:
-Xms128M -Xmx2048M

Сначала прикрепите тестовый код:

import org.junit.Test;

import java.util.ArrayList;
import java.util.List;

public class MemoryRecycleTest {

    @Test
    public void testMemoryRecycle() throws InterruptedException {

        List list = new ArrayList();

        //指定要生产的对象大小为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();

        //阻止程序退出
        Thread.currentThread().join();
    }

    public void addObject(List 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("");
    }

}

Сначала используйте сборщик мусора CMS:

  • Установите параметры запуска jvm следующим образом:
-Xms128M -Xmx2048M -XX:+UseConcMarkSweepGC

image

  • После запуска программы используйте JProfiler для просмотра динамической памяти:

image

  • Посмотрите, что выводит консоль:
第1次生产512大小的对象
清理list.... 回收jvm内存....

JVM内存已用的空间为:6 MB
JVM内存的空闲空间为:936 MB
JVM总内存空间为:942 MB
JVM总内存最大堆空间为:1990 MB

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

JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:1025 MB
JVM总内存空间为:1029 MB
JVM总内存最大堆空间为:1990 MB

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

JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:680 MB
JVM总内存空间为:684 MB
JVM总内存最大堆空间为:1990 MB

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

JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:119 MB
JVM总内存空间为:123 MB
JVM总内存最大堆空间为:1990 MB

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

JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:119 MB
JVM总内存空间为:123 MB
JVM总内存最大堆空间为:1990 MB

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

JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:119 MB
JVM总内存空间为:123 MB
JVM总内存最大堆空间为:1990 MB

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

JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:119 MB
JVM总内存空间为:123 MB
JVM总内存最大堆空间为:1990 MB

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

JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:119 MB
JVM总内存空间为:123 MB
JVM总内存最大堆空间为:1990 MB

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

JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:119 MB
JVM总内存空间为:123 MB
JVM总内存最大堆空间为:1990 MB

  • Просмотр информации о куче jmap:
C:\Users>jmap -heap 4716
Attaching to process ID 4716, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.161-b12

using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 2122317824 (2024.0MB)
   NewSize                  = 44695552 (42.625MB)
   MaxNewSize               = 348913664 (332.75MB)
   OldSize                  = 89522176 (85.375MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
   capacity = 280887296 (267.875MB)
   used     = 1629392 (1.5539093017578125MB)
   free     = 279257904 (266.3210906982422MB)
   0.5800874668251284% used
Eden Space:
   capacity = 249692160 (238.125MB)
   used     = 1629392 (1.5539093017578125MB)
   free     = 248062768 (236.5710906982422MB)
   0.6525603366961942% used
From Space:
   capacity = 31195136 (29.75MB)
   used     = 0 (0.0MB)
   free     = 31195136 (29.75MB)
   0.0% used
To Space:
   capacity = 31195136 (29.75MB)
   used     = 0 (0.0MB)
   free     = 31195136 (29.75MB)
   0.0% used
concurrent mark-sweep generation:
   capacity = 624041984 (595.1328125MB)
   used     = 4169296 (3.9761505126953125MB)
   free     = 619872688 (591.1566619873047MB)
   0.6681114583470076% used

6718 interned Strings occupying 574968 bytes.

По статистической диаграмме и журналу консоли видно, что до запуска в течение примерно 43 секунд используемая память увеличивается линейно и плавно, а освоенная память увеличивается ступенчато. Когда используемая память достигает 525 м, программа инициирует System.gc(), и в это время собирается мусор, поэтому используемая память возвращается к 10 м, но пространство памяти, открытое jvm, не возвращается в операционную систему, в результате программа занимает 960m все время о ресурсах памяти. Когда объект создается во второй раз, видно, что с 53 секунд до 1 минуты 44 секунд новое пространство не открывается, но открытая память повторно используется для продолжения создания объектов. .gc() выполняется, JVM открыла небольшую часть памяти, на этот раз программа занимает 1050м ресурсов памяти. Когда объект создается в третий раз, видно, что когда он работает от 2 минут 05 секунд до 2 минут 55 секунд, новое пространство не открывается, но открытая память повторно используется для продолжения создания объектов. выполненный в третий раз, System.gc(), JVM возвращает часть памяти операционной системе, и она все еще занимает в это время 700м памяти. ........ Цикл выполняется 10 раз... Из общей ситуации видно, что по мере постепенного увеличения числа System.gc() и постепенного увеличения временного интервала он меняется с продолжающегося на откройте память для Затем медленно верните память в операционную систему, пока вся физическая память не будет возвращена в операционную систему позже.

Затем используйте сборщик мусора G1:

-Xms128M -Xmx2048M -XX:+UseG1GC

image

  • После запуска программы используйте JProfiler для просмотра динамической памяти:

image

  • Посмотрите, что выводит консоль:
第1次生产512大小的对象
清理list.... 回收jvm内存....

JVM内存已用的空间为:5 MB
JVM内存的空闲空间为:123 MB
JVM总内存空间为:128 MB
JVM总内存最大堆空间为:2024 MB

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

JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:124 MB
JVM总内存空间为:128 MB
JVM总内存最大堆空间为:2024 MB

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

JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:124 MB
JVM总内存空间为:128 MB
JVM总内存最大堆空间为:2024 MB

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

JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:124 MB
JVM总内存空间为:128 MB
JVM总内存最大堆空间为:2024 MB

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

JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:124 MB
JVM总内存空间为:128 MB
JVM总内存最大堆空间为:2024 MB

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

JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:124 MB
JVM总内存空间为:128 MB
JVM总内存最大堆空间为:2024 MB

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

JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:124 MB
JVM总内存空间为:128 MB
JVM总内存最大堆空间为:2024 MB

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

JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:124 MB
JVM总内存空间为:128 MB
JVM总内存最大堆空间为:2024 MB

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

JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:124 MB
JVM总内存空间为:128 MB
JVM总内存最大堆空间为:2024 MB

  • Просмотр информации о куче jmap:
C:\Users>jmap -heap 18112
Attaching to process ID 18112, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.161-b12

using thread-local object allocation.
Garbage-First (G1) GC with 4 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 2122317824 (2024.0MB)
   NewSize                  = 1363144 (1.2999954223632812MB)
   MaxNewSize               = 1272971264 (1214.0MB)
   OldSize                  = 5452592 (5.1999969482421875MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 1048576 (1.0MB)

Heap Usage:
G1 Heap:
   regions  = 2024
   capacity = 2122317824 (2024.0MB)
   used     = 8336616 (7.950416564941406MB)
   free     = 2113981208 (2016.0495834350586MB)
   0.39280714253663074% used
G1 Young Generation:
Eden Space:
   regions  = 2
   capacity = 83886080 (80.0MB)
   used     = 2097152 (2.0MB)
   free     = 81788928 (78.0MB)
   2.5% used
Survivor Space:
   regions  = 0
   capacity = 0 (0.0MB)
   used     = 0 (0.0MB)
   free     = 0 (0.0MB)
   0.0% used
G1 Old Generation:
   regions  = 11
   capacity = 50331648 (48.0MB)
   used     = 6239464 (5.950416564941406MB)
   free     = 44092184 (42.049583435058594MB)
   12.396701176961264% used

6706 interned Strings occupying 573840 bytes.

По статистической диаграмме и журналу консоли видно, что до запуска в течение примерно 41 секунды используемая память увеличивается линейно и плавно, а открытая память также увеличивается линейно и плавно. Когда используемая память достигает 530 м, программа инициирует System.gc(), и мусор собирается, поэтому используемая память возвращается к 10 м. В этот момент будет обнаружено волшебное явление: все оставшееся пространство памяти, открытое jvm, возвращается в операционную систему, а память возвращается к исходному размеру кучи jvm, который мы указали 128 м. Путем многократного сравнения производственных объектов обнаружено, что JVM возвращает всю физическую память после каждого вызова System.gc() без каких-либо резервов. Достиг ожидаемого эффекта!

Суммировать:

Сборщик мусора CMS, после открытия памяти, будет постепенно увеличивать количество запусков System.gc() и частоту восстановления постепенно, возвращая всю оставшуюся физическую память операционной системе каждый раз, когда System.gc() будет выполняться. вызывается; G1 как раз наоборот.G1 активно возвращает физическую память операционной системе после каждой сборки мусора JVM без какого-либо резервирования, что значительно снижает использование памяти.

Кроме того, для просмотра состояния стека Java в режиме реального времени рекомендуется использовать JProfiler и VisualVM. Если он локальный, рекомендуется JProfiler из-за его мощных функций, но удаленная настройка затруднительна; если он подключен к удаленному java-процессу, рекомендуется VisualVM, функция доступна, и вам нужно только настроить некоторые параметры jvm для подключения удаленно.

другие инструкции

JDK 12 будет иметь сборщик G1, возвращающий память ОС (без вызова System.gc) «когда приложение простаивает»

jdk9 增加了这个jvm参数:

-XX:+ShrinkHeapInSteps
使Java堆渐进地缩小到目标大小,该选项默认开启,经过多次GC后堆缩小到目标大小;如果关闭该选项,那么GC后Java堆将立即缩小到目标大小。如果希望最小化Java堆大小,可以关闭改选项,并配合以下选项:

-XX:MaxHeapFreeRatio=10 -XX:MinHeapFreeRatio=5

这样将保持Java堆空间较小,并减少程序的动态占用空间,这对嵌入式应用非常有用,但对于一般应用,可能降低性能。

Использованная литература:

Woohoo.IMO OC.com/Q&A/platform… developer.IBM.com/talent/blog/201… игровой институт.QQ.com/community/… Ууху. Call.com/question/30… Ууху. Call.com/question/29…