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


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

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

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

и все еще вижу это в VisualVM:

Visual VM memory usage

ясно, что 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 на подкачку близко к мусору 2GB и выходить-это просто пустая трата ресурсов. Если вы не согласны, пожалуйста, просветите меня.

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

Edit: Аугусто указал, что поиск привел бы меня к -XX:MaxHeapFreeRatio и -XX:MinHeapFreeRatio работали с -XX:+UseSerialGC. Я был в восторге и попробовал, озадаченный тем, что сам этого не нашел. Да, это действительно сработало с моей памятью.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 комментарий для исправленной проблемы говорит:

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

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

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

3   51   2015-10-24 16:22:20

3 ответа:

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

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

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

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

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

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

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

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