Подтвердить что ты не робот

Будет ли OpenJDK JVM когда-либо давать кучу памяти обратно в Linux?

У нас есть долгоживущий серверный процесс, который редко требуется много оперативной памяти в течение короткого времени. Мы видим, что, как только JVM получит память из ОС, это никогда не возвращает его обратно в ОС. Как мы попросим JVM вернуть память кучи обратно в ОС?

Как правило, принятый ответ на такие вопросы заключается в использовании -XX:MaxHeapFreeRatio и -XX:MinHeapFreeRatio. (См. 1, 2, 3, 4). Но мы запускаем java следующим образом:

java -Xmx4G -XX:MaxHeapFreeRatio=50 -XX:MinHeapFreeRatio=30 MemoryUsage

и все еще видите это в VisualVM:

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

Ясно, что JVM не чтит -XX:MaxHeapFreeRatio=50, поскольку heapFreeRatio очень близок к 100% и нигде около 50%. Никакое количество нажатий на "Выполнить GC" не возвращает память в ОС.

MemoryUsage.java:

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

public class MemoryUsage {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("Sleeping before allocating memory");
        Thread.sleep(10*1000);

        System.out.println("Allocating/growing memory");
        List<Long> list = new ArrayList<>();
        // Experimentally determined factor. This gives approximately 1750 MB
        // memory in our installation.
        long realGrowN = 166608000; //
        for (int i = 0 ; i < realGrowN ; i++) {
            list.add(23L);
        }

        System.out.println("Memory allocated/grown - sleeping before Garbage collecting");
        Thread.sleep(10*1000);

        list = null;
        System.gc();

        System.out.println("Garbage collected - sleeping forever");
        while (true) {
            Thread.sleep(1*1000);
        }
    }
}

Версии:

> java -version
openjdk version "1.8.0_66-internal"
OpenJDK Runtime Environment (build 1.8.0_66-internal-b01)
OpenJDK 64-Bit Server VM (build 25.66-b01, mixed mode)

> uname -a
Linux londo 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt11-1+deb8u5 (2015-10-09) x86_64 GNU/Linux

> lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description:    Debian GNU/Linux 8.2 (jessie)
Release:    8.2
Codename:   jessie

Я также пробовал OpenJDK 1.7 и Sun Java 1.8. Все ведут себя одинаково, и никто вернуть память в ОС.

Мне кажется, что мне нужно это, и этот обмен и пейджинг не будут "решать" это, потому что тратить диск IO на пейджинг, близкий к мусорному ядру 2 ГБ, это просто пустая трата ресурсов. Если вы не согласны, пожалуйста, просветите меня.

Я также написал немного memoryUsage.c с malloc()/free(), и он возвращает память в ОС. Так можно в C. Возможно, не с Java?

Изменить: Августо указал, что поиск привел бы меня к -XX:MaxHeapFreeRatio и -XX:MinHeapFreeRatio, работающему только с -XX:+UseSerialGC. Я был в восторге и испробовал это, озадаченный тем, что сам этого не нашел. Да, он работал с моим MemoryUsage.java:

-XX:+UseSerialGC working with simple app

Однако, когда я пробовал -XX:+UseSerialGC с нашим реальным приложением, не так много:

-XX:+UseSerialGC not working with real app

Я обнаружил, что gc() через некоторое время помог, поэтому я сделал поток, который сделал больше или меньше:

while (idle() && memoryTooLarge() && ! tooManyAttemptsYet()) {
    Thread.sleep(10*1000);
    System.gc();
}

и это сделало трюк:

GC thread working

Ранее я видел поведение с -XX:+UseSerialGC и несколькими вызовами System.gc() в некоторых из моих многочисленных экспериментов, но мне не нравилась необходимость в потоке GC. И кто знает, продолжит ли это работать, как развиваются наше приложение, так и java. Должен быть лучший способ.

Какова логика, которая заставляет меня звонить System.gc() четыре раза (но не сразу), и где этот материал задокументирован?

В поисках документации для -XX:MaxHeapFreeRatio и -XX:MinHeapFreeRatio, работающей только с -XX:+UseSerialGC, я читаю документацию для java-инструмента/исполняемого файла и нигде не упоминается, что -XX:MaxHeapFreeRatio и -XX:MinHeapFreeRatio работает только с -XX:+UseSerialGC. Фактически, исправленная проблема [JDK-8028391] Сделать флаги Min/MaxHeapFreeRatio управляемыми говорит:

Чтобы разрешить приложениям контролировать, как и когда разрешать более или менее GC, флаги -XX: MinHeapFreeRatio и -XX: MaxHeapFreeRatio должны быть сделаны управляемый. Поддержка этих флагов также должна быть реализована в параллельный коллектор по умолчанию.

A comment для исправленной проблемы говорит:

Поддержка этих флагов также добавлена ​​в ParallelGC как часть политики адаптивного размера.

Я проверил, и патч, на который ссылается исправленная проблема, обращенная к openjdk-8, действительно содержится в исходный пакет tarball для версии openjdk-8, которую я использую. Поэтому он должен, по-видимому, работать в "параллельном сборщике по умолчанию", но не так, как я продемонстрировал в этом посте. Я еще не нашел документацию, в которой говорится, что она должна работать только с -XX:+UseSerialGC. И поскольку я документировал здесь, даже это ненадежно /dicey.

Не могу ли я получить -XX:MaxHeapFreeRatio и -XX:MinHeapFreeRatio, чтобы выполнить то, что они обещают, не пройдя все эти обручи?

4b9b3361

Ответ 1

"G1 (-XX: + UseG1GC), Parallel scavenge (-XX: + UseParallelGC) и ParallelOld (-XX: + UseParallelOldGC) возвращают память, когда куча сжимается. Я не уверен в Serial и CMS, они не сокращали свою кучу в моих экспериментах.

Оба параллельных коллектора требуют нескольких GC, прежде чем уменьшать кучу до "приемлемого" размера. Это за дизайн. Они намеренно держатся за кучу, полагая, что это будет необходимо в будущем. Установка флага -XX: GCTimeRatio = 1 несколько улучшит ситуацию, но все равно потребуется несколько GC для сжатия.

G1 замечательно хорош в сжатии кучи быстро, поэтому для описанной выше утилиты я бы сказал, что она разрешима с помощью G1 и запускает System.gc() после освобождения всех кэшей и загрузчиков классов и т.д. "

http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6498735

Ответ 2

Если есть способ (скажем, m1()), который делает тяжелую работу в вашем приложении, вы можете попробовать следующее:

  • вместо вызова m1(), serialize всех необходимых объектов для работы метода.
  • создайте основной метод, который de-serialize объекты, которые вы сериализовали на предыдущем шаге. и вызывает m1() - этот главный метод является точкой входа для программы P1.
  • По завершении m1() сериализуйте вывод для основной программы для де-сериализации.
  • выполнить P1 как отдельную программу с помощью Runtime or ProcessBuilder.

Таким образом, когда метод тяжелой работы m1() завершается, процесс P1 завершится, что должно освободить кучу на этой JVM.

Ответ 3

Ваша проблема - сложная задача - я бы предложил решение легко. Из того, что я прочитал, вы знаете, как кодировать как на C, так и на Java. Может быть использование JNA (Java Native Access) или JNI (Java Native Interface) может разрешить корень проблемы (кодирование тяжелой части обработки на C и вызов ее из java). Другим способом было бы сделать еще одну небольшую программу, написанную на C, сделать тяжелую работу - вы бы назвали эту небольшую программу из своего java-кода (предпочтительно в своем потоке). Родственный доступ Java Явный интерфейс Java