У нас есть долгоживущий серверный процесс, который редко требуется много оперативной памяти в течение короткого времени. Мы видим, что, как только JVM получит память из ОС, это никогда не возвращает его обратно в ОС. Как мы попросим JVM вернуть память кучи обратно в ОС?
Как правило, принятый ответ на такие вопросы заключается в использовании
-XX:MaxHeapFreeRatio
и -XX:MinHeapFreeRatio
. (См.
1, 2, 3, 4). Но мы запускаем java следующим образом:
java -Xmx4G -XX:MaxHeapFreeRatio=50 -XX:MinHeapFreeRatio=30 MemoryUsage
и все еще видите это в VisualVM:
Ясно, что 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
с нашим реальным приложением, не так много:
Я обнаружил, что gc() через некоторое время помог, поэтому я сделал поток, который сделал больше или меньше:
while (idle() && memoryTooLarge() && ! tooManyAttemptsYet()) {
Thread.sleep(10*1000);
System.gc();
}
и это сделало трюк:
Ранее я видел поведение с -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
, чтобы выполнить то, что они обещают, не пройдя все эти обручи?