Для моего приложения память, используемая процессом Java, намного больше, чем размер кучи.
В системе, в которой работают контейнеры, возникают проблемы с памятью, поскольку контейнер занимает гораздо больше памяти, чем размер кучи.
Размер кучи установлен на 128 МБ ( -Xmx128m -Xms128m
), в то время как контейнер занимает до 1 ГБ памяти. В нормальных условиях требуется 500 МБ. Если контейнер докеров имеет ограничение ниже (например mem_limit=mem_limit=400MB
), процесс завершается убийцей нехватки памяти ОС.
Не могли бы вы объяснить, почему процесс Java использует гораздо больше памяти, чем куча? Как правильно определить лимит памяти Docker? Есть ли способ уменьшить объем памяти вне кучи процесса Java?
Я собираю некоторые подробности о проблеме, используя команду отслеживания собственной памяти в JVM .
Из хост-системы я получаю память, используемую контейнером.
$ docker stats --no-stream 9afcb62a26c8
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
9afcb62a26c8 xx-xxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.0acbb46bb6fe3ae1b1c99aff3a6073bb7b7ecf85 0.93% 461MiB / 9.744GiB 4.62% 286MB / 7.92MB 157MB / 2.66GB 57
Изнутри контейнера я получаю память, используемую процессом.
$ ps -p 71 -o pcpu,rss,size,vsize
%CPU RSS SIZE VSZ
11.2 486040 580860 3814600
$ jcmd 71 VM.native_memory
71:
Native Memory Tracking:
Total: reserved=1631932KB, committed=367400KB
- Java Heap (reserved=131072KB, committed=131072KB)
(mmap: reserved=131072KB, committed=131072KB)
- Class (reserved=1120142KB, committed=79830KB)
(classes #15267)
( instance classes #14230, array classes #1037)
(malloc=1934KB #32977)
(mmap: reserved=1118208KB, committed=77896KB)
( Metadata: )
( reserved=69632KB, committed=68272KB)
( used=66725KB)
( free=1547KB)
( waste=0KB =0.00%)
( Class space:)
( reserved=1048576KB, committed=9624KB)
( used=8939KB)
( free=685KB)
( waste=0KB =0.00%)
- Thread (reserved=24786KB, committed=5294KB)
(thread #56)
(stack: reserved=24500KB, committed=5008KB)
(malloc=198KB #293)
(arena=88KB #110)
- Code (reserved=250635KB, committed=45907KB)
(malloc=2947KB #13459)
(mmap: reserved=247688KB, committed=42960KB)
- GC (reserved=48091KB, committed=48091KB)
(malloc=10439KB #18634)
(mmap: reserved=37652KB, committed=37652KB)
- Compiler (reserved=358KB, committed=358KB)
(malloc=249KB #1450)
(arena=109KB #5)
- Internal (reserved=1165KB, committed=1165KB)
(malloc=1125KB #3363)
(mmap: reserved=40KB, committed=40KB)
- Other (reserved=16696KB, committed=16696KB)
(malloc=16696KB #35)
- Symbol (reserved=15277KB, committed=15277KB)
(malloc=13543KB #180850)
(arena=1734KB #1)
- Native Memory Tracking (reserved=4436KB, committed=4436KB)
(malloc=378KB #5359)
(tracking overhead=4058KB)
- Shared class space (reserved=17144KB, committed=17144KB)
(mmap: reserved=17144KB, committed=17144KB)
- Arena Chunk (reserved=1850KB, committed=1850KB)
(malloc=1850KB)
- Logging (reserved=4KB, committed=4KB)
(malloc=4KB #179)
- Arguments (reserved=19KB, committed=19KB)
(malloc=19KB #512)
- Module (reserved=258KB, committed=258KB)
(malloc=258KB #2356)
$ cat /proc/71/smaps | grep Rss | cut -d: -f2 | tr -d " " | cut -f1 -dk | sort -n | awk '{ sum += $1 } END { print sum }'
491080
Приложение представляет собой веб-сервер, использующий Jetty / Jersey / CDI, заключенный в пакет размером 36 МБ.
Используются следующие версии ОС и Java (внутри контейнера). Образ Docker основан на openjdk:11-jre-slim
.
$ java -version
openjdk version "11" 2018-09-25
OpenJDK Runtime Environment (build 11+28-Debian-1)
OpenJDK 64-Bit Server VM (build 11+28-Debian-1, mixed mode, sharing)
$ uname -a
Linux service1 4.9.125-linuxkit #1 SMP Fri Sep 7 08:20:28 UTC 2018 x86_64 GNU/Linux
https://gist.github.com/prasanthj/48e7063cac88eb396bc9961fb3149b58
cgroups
добавляет дисковый кеш к используемой памяти - даже если он обрабатывается ядром и невидим для пользовательской программы. (Имейте в виду, командыps
иdocker stats
неОтветы:
Виртуальная память, используемая Java-процессом, выходит далеко за пределы Java Heap. Вы знаете, JVM включает в себя множество подсистем: сборщик мусора, загрузка классов, JIT-компиляторы и т. Д., И всем этим подсистемам для работы требуется определенное количество ОЗУ.
JVM - не единственный потребитель оперативной памяти. Собственные библиотеки (включая стандартную библиотеку классов Java) также могут выделять собственную память. И это даже не будет видно при отслеживании встроенной памяти. Само приложение Java также может использовать память вне кучи с помощью прямых ByteBuffers.
Итак, что требует памяти в процессе Java?
Части JVM (в основном отображаются с помощью отслеживания встроенной памяти)
Куча Java
Самая очевидная часть. Здесь живут объекты Java. Куча занимает до
-Xmx
объема памяти.Уборщик мусора
Структуры и алгоритмы сборщика мусора требуют дополнительной памяти для управления кучей. Это структуры Mark Bitmap, Mark Stack (для обхода графа объекта), Remembered Sets (для записи межрегиональных ссылок) и другие. Некоторые из них настраиваются напрямую, например
-XX:MarkStackSizeMax
, другие зависят от структуры кучи, например, чем больше области G1 (-XX:G1HeapRegionSize
), тем меньшие запоминаются наборы.Накладные расходы памяти GC варьируются в зависимости от алгоритма GC.
-XX:+UseSerialGC
и-XX:+UseShenandoahGC
иметь наименьшие накладные расходы. G1 или CMS могут легко использовать около 10% от общего размера кучи.Кэш кода
Содержит динамически сгенерированный код: JIT-скомпилированные методы, интерпретатор и временные заглушки. Его размер ограничен
-XX:ReservedCodeCacheSize
(по умолчанию 240M). Выключите,-XX:-TieredCompilation
чтобы уменьшить объем скомпилированного кода и, следовательно, использование кэша кода.составитель
Сам JIT-компилятор также требует памяти для выполнения своей работы. Это может быть уменьшено снова путем выключения многоуровневой компиляции или путем уменьшения числа потоков компилятора:
-XX:CICompilerCount
.Загрузка класса
Метаданные класса (байт-коды методов, символы, пулы констант, аннотации и т. Д.) Хранятся в области вне кучи, называемой Metaspace. Чем больше классов загружено - тем больше используется метапространство. Общее использование может быть ограничено
-XX:MaxMetaspaceSize
(по умолчанию без ограничений) и-XX:CompressedClassSpaceSize
(по умолчанию 1 ГБ).Таблицы символов
Две основные хэш-таблицы JVM: таблица символов содержит имена, подписи, идентификаторы и т. Д., А таблица String содержит ссылки на интернированные строки. Если отслеживание собственной памяти указывает на значительное использование памяти таблицей String, это, вероятно, означает, что приложение вызывает чрезмерное количество вызовов
String.intern
.Потоки
Стеки потоков также отвечают за использование оперативной памяти. Размер стека контролируется
-Xss
. По умолчанию 1M на поток, но, к счастью, все не так плохо. ОС выделяет страницы памяти лениво, т.е. при первом использовании, поэтому фактическое использование памяти будет намного меньше (обычно 80-200 КБ на стек потока). Я написал сценарий, чтобы оценить, какая часть RSS принадлежит стеку потоков Java.Существуют и другие части JVM, которые выделяют внутреннюю память, но обычно они не играют большой роли в общем потреблении памяти.
Прямые буферы
Приложение может явно запросить память вне кучи, вызвав
ByteBuffer.allocateDirect
. Предел по умолчанию вне кучи равен-Xmx
, но его можно переопределить с помощью-XX:MaxDirectMemorySize
. Прямые байтовые буферы включены вOther
раздел вывода NMT (илиInternal
до JDK 11).Объем используемой прямой памяти виден через JMX, например, в JConsole или Java Mission Control:
Помимо прямых ByteBuffers могут быть
MappedByteBuffers
- файлы, отображаемые в виртуальную память процесса. NMT не отслеживает их, однако MappedByteBuffers также может занимать физическую память. И нет простого способа ограничить их количество. Вы можете просто увидеть фактическое использование, посмотрев на карту памяти процесса:pmap -x <pid>
Родные библиотеки
Код JNI, загруженный с помощью,
System.loadLibrary
может выделить столько памяти вне кучи, сколько захочет, без какого-либо контроля со стороны JVM. Это касается и стандартной библиотеки классов Java. В частности, незакрытые ресурсы Java могут стать источником утечки собственной памяти. Типичные примеры -ZipInputStream
илиDirectoryStream
.Агенты JVMTI, в частности
jdwp
агент отладки, также могут вызывать чрезмерное потребление памяти.В этом ответе описывается, как профилировать выделение собственной памяти с помощью async-profiler .
Проблемы с распределителем
Обычно процесс запрашивает внутреннюю память либо непосредственно из ОС (
mmap
системным вызовом), либо с помощьюmalloc
стандартного распределителя libc. В свою очередь,malloc
запрашивает у ОС большие куски памятиmmap
, а затем управляет этими кусками в соответствии со своим собственным алгоритмом распределения. Проблема в том, что этот алгоритм может привести к фрагментации и чрезмерному использованию виртуальной памяти .jemalloc
, альтернативный распределитель, часто кажется более умным, чем обычная libcmalloc
, поэтому переход на негоjemalloc
может привести к уменьшению занимаемой площади бесплатно.Вывод
Нет гарантированного способа оценить полное использование памяти процессом Java, потому что необходимо учитывать слишком много факторов.
Можно сжимать или ограничивать определенные области памяти (например, кэш кода) с помощью флагов JVM, но многие другие вообще находятся вне контроля JVM.
Один из возможных подходов к установке ограничений Docker - это наблюдение за фактическим использованием памяти в «нормальном» состоянии процесса. Существуют инструменты и методы для исследования проблем с потреблением памяти Java: Native Memory Tracking , pmap , jemalloc , async-profiler. .
Обновить
Вот запись моей презентации Объем памяти Java-процесса .
В этом видео я обсуждаю, что может потреблять память в процессе Java, как отслеживать и ограничивать размер определенных областей памяти и как профилировать утечки собственной памяти в приложении Java.
источник
https://developers.redhat.com/blog/2017/04/04/openjdk-and-containers/ :
Java видит размер памяти хоста и не знает никаких ограничений памяти контейнера. Это не создает давления на память, поэтому GC также не нужно освобождать используемую память. Надеюсь,
XX:MaxRAM
это поможет вам уменьшить объем памяти. В конце концов, вы можете настроить конфигурацию GC (-XX:MinHeapFreeRatio
,-XX:MaxHeapFreeRatio
, ...)Есть много типов показателей памяти. Docker, кажется, сообщает размер RSS-памяти, который может отличаться от "фиксированной" памяти, о которой сообщает
jcmd
(более старые версии Docker сообщают RSS + cache как использование памяти). Хорошее обсуждение и ссылки: Разница между размером резидентного набора (RSS) и общим объемом выделенной памяти Java (NMT) для JVM, работающей в контейнере Docker(RSS) память также может быть съедена некоторыми другими утилитами в контейнере - оболочкой, диспетчером процессов, ... Мы не знаем, что еще работает в контейнере и как вы запускаете процессы в контейнере.
источник
-XX:MaxRam
. Я думаю, что он все еще использует больше, чем указано в максимуме, но это лучше, спасибо!-Xmx128m -Xms128m -Xss228k -XX:MaxRAM=256m -XX:+UseSerialGC
, производитDocker 428.5MiB / 600MiB
иjcmd 58 VM.native_memory -> Native Memory Tracking: Total: reserved=1571296KB, committed=314316KB
. JVM занимает около 300 МБ, а контейнеру требуется 430 МБ. Где 130 МБ между отчетами JVM и отчетами ОС?ps -p 71 -o pcpu,rss,size,vsize
с процессом Java, имеющим pid 71. На самом деле-XX:MaxRam
это не помогло, но предоставленная вами ссылка помогает с последовательным GC.TL; DR
Подробное использование памяти обеспечивается подробностями Native Memory Tracking (NMT) (в основном метаданными кода и сборщиком мусора). В дополнение к этому компилятор Java и оптимизатор C1 / C2 потребляют память, не указанную в сводке.
Объем памяти можно уменьшить с помощью флагов JVM (но это оказывает влияние).
Определение размера контейнера Docker должно выполняться путем тестирования с ожидаемой нагрузкой приложения.
Детали для каждого компонента
Общий класс пространство может быть отключено внутри контейнера , так как классы не будут передаваться другим процессом виртуальной машины Java. Можно использовать следующий флаг. Это удалит общее пространство класса (17 МБ).
Сборщик мусора серийный имеет минимальный объем памяти за счет более длительного времени паузы во время сборки мусора обработки (см сравнение Алексей Shipilëv между GC в одном изображении ). Его можно включить с помощью следующего флага. Он может сэкономить до используемого пространства GC (48 МБ).
С2 компилятор может быть отключен со следующим флагом для уменьшения профилирования данных , используемых , чтобы решить , следует ли оптимизировать или нет метода.
Размер кода уменьшен на 20 МБ. Кроме того, память вне JVM уменьшается на 80 МБ (разница между пространством NMT и пространством RSS). Оптимизирующему компилятору C2 требуется 100 МБ.
В Составители C1 и C2 может быть отключена с помощью следующего флага.
Объем памяти вне JVM теперь меньше общего выделенного пространства. Размер кода уменьшен на 43 МБ. Остерегайтесь, это сильно влияет на производительность приложения.Отключение компилятора C1 и C2 уменьшает используемую память на 170 МБ.
Использование компилятора Graal VM (замена C2) приводит к немного меньшему объему памяти. Он увеличивает на 20 МБ пространство памяти кода и уменьшает на 60 МБ внешнюю память JVM.
Статья « Управление памятью Java для JVM» предоставляет некоторую важную информацию о различных областях памяти. Oracle предоставляет некоторые подробности в документации по отслеживанию встроенной памяти . Дополнительные сведения об уровне компиляции в расширенной политике компиляции и в разделе «Отключить C2» уменьшают размер кэша кода в 5 раз . Некоторые подробности о том, почему JVM сообщает о большем объеме выделенной памяти, чем размер резидентного набора процесса Linux? когда оба компилятора отключены.
источник
Java требует много памяти. Самой JVM для работы требуется много памяти. Куча - это память, которая доступна внутри виртуальной машины и доступна вашему приложению. Поскольку JVM - это большой пакет, наполненный всеми возможными полезностями, просто для загрузки требуется много памяти.
Начиная с java 9 у вас есть что-то, называемое Project Jigsaw , которое может уменьшить объем памяти, используемый при запуске приложения Java (вместе со временем запуска). Головоломка проекта и новая система модулей не обязательно создавались для уменьшения необходимой памяти, но если это важно, вы можете попробовать.
Вы можете взглянуть на этот пример: https://steveperkins.com/using-java-9-modularization-to-ship-zero-dependency-native-apps/ . Благодаря использованию модульной системы было получено приложение CLI размером 21 МБ (со встроенной JRE). JRE занимает более 200мб. Это должно привести к уменьшению объема выделенной памяти при запуске приложения (множество неиспользуемых классов JRE больше не будут загружаться).
Вот еще один хороший учебник: https://www.baeldung.com/project-jigsaw-java-modularity
Если вы не хотите тратить на это время, вы можете просто выделить больше памяти. Иногда это лучше всего.
источник
jlink
довольно ограничено, поскольку требует, чтобы приложение было модульным. Автоматический модуль не поддерживается, поэтому нет простого пути туда.Как правильно определить лимит памяти Docker? Проверьте приложение, некоторое время наблюдая за ним. Чтобы ограничить память контейнера, попробуйте использовать параметр -m, --memory bytes для команды запуска docker - или что-то эквивалентное, если вы запускаете его иначе, например
не могу ответить на другие вопросы.
источник