Для моего приложения память, используемая процессом 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
Похоже, сборщик мусора использует много памяти. Вместо этого вы можете попробовать использовать сборщик CMS. Похоже, что ~ 125 МБ используется для метапространства + кода, однако без сокращения базы кода вы вряд ли сможете сделать это меньше. Выделенное пространство близко к вашему пределу, поэтому неудивительно, что его убивают.
где / как установить конфигурацию -Xms и -Xmx?
В командной строке Java java -Xmx128m -Xms128m -jar the-bundle-package.jar
Думаю, стоит упомянуть эту статью: Поддержка Java SE для ЦП Docker и ограничений памяти
Примечание: Java 8 u212 + теперь (апрель 2019 г.) имеет лучшую поддержку Docker, когда дело касается памяти
Программа выполняет много файловых операций (например, создает файлы размером в гигабайт)? Если это так, вы должны знать, что cgroups добавляет дисковый кеш к используемой памяти - даже если он обрабатывается ядром и невидим для пользовательской программы. (Имейте в виду, команды ps и docker stats не учитывают дисковый кеш.)
Он записывает журналы, но не в размере ГБ, это веб-приложение, основанное на Джерси / CDI, похожее на этот пакет, опубликованный в GitHub
Если вы не знаете, что именно такое MiB и GiB в ответ на получение памяти, используемой контейнером из хост-системы: askubuntu.com/questions/22102/meaning-of-i-in-mib




Java требует много памяти. Сама JVM требует много памяти для работы. Куча - это память, доступная внутри виртуальной машины, доступная вашему приложению. Поскольку JVM - это большой пакет, в котором есть все, что только можно, просто для загрузки требуется много памяти.
Начиная с Java 9 у вас есть что-то под названием проект 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 довольно ограничено, поскольку требует модульного построения приложения. Автоматический модуль не поддерживается, поэтому нет простого пути туда.
https://developers.redhat.com/blog/2017/04/04/openjdk-and-containers/:
Why is it when I specify -Xmx=1g my JVM uses up more memory than 1gb of memory?
Specifying -Xmx=1g is telling the JVM to allocate a 1gb heap. It’s not telling the JVM to limit its entire memory usage to 1gb. There are card tables, code caches, and all sorts of other off heap data structures. The parameter you use to specify total memory usage is -XX:MaxRAM. Be aware that with -XX:MaxRam=500m your heap will be approximately 250mb.
Java видит размер памяти хоста и не знает никаких ограничений памяти контейнера. Это не создает давления на память, поэтому GC также не нужно освобождать используемую память. Я надеюсь, что XX:MaxRAM поможет вам уменьшить объем памяти. В конце концов, вы можете настроить конфигурацию GC (-XX:MinHeapFreeRatio, -XX:MaxHeapFreeRatio, ...)
Есть много типов показателей памяти. Docker, кажется, сообщает размер RSS-памяти, который может отличаться от "фиксированной" памяти, сообщаемой jcmd (более старые версии Docker сообщают RSS + cache как использование памяти).
Хорошее обсуждение и ссылки: Разница между размером резидентного набора (RSS) и общим объемом выделенной памяти Java (NMT) для JVM, работающей в контейнере Docker
(RSS) память также может быть съедена некоторыми другими утилитами в контейнере - оболочкой, диспетчером процессов, ... Мы не знаем, что еще работает в контейнере и как вы запускаете процессы в контейнере.
С -XX:MaxRam действительно лучше. Я думаю, что он все еще использует больше, чем указано в максимуме, но это лучше, спасибо!
Возможно, вам действительно нужно больше памяти для этого экземпляра Java. Всего 15267 классов, 56 потоков.
Предоставленный RSS находится внутри контейнера только для процесса Java ps -p 71 -o pcpu,rss,size,vsize с процессом Java с pid 71. На самом деле -XX:MaxRam не помог, но предоставленная вами ссылка помогает с последовательным GC.
Как правильно определить лимит памяти Docker? Проверьте приложение, наблюдая за ним в течение некоторого времени. Чтобы ограничить память контейнера, попробуйте использовать параметр -m, --memory bytes для команды запуска docker - или что-то эквивалентное, если вы его запускаете в противном случае как
docker run -d --name my-container --memory 500m <iamge-name>
не могу ответить на другие вопросы.
Подробное использование памяти обеспечивается подробностями Native Memory Tracking (NMT) (в основном, метаданными кода и сборщиком мусора). В дополнение к этому компилятор Java и оптимизатор C1 / C2 потребляют память, не указанную в сводке.
Объем памяти можно уменьшить с помощью флагов JVM (но это оказывает влияние).
Определение размера контейнера Docker должно производиться путем тестирования с ожидаемой нагрузкой приложения.
общее классное пространство можно отключить внутри контейнера, поскольку классы не будут совместно использоваться другим процессом JVM. Можно использовать следующий флаг. Это приведет к удалению общего пространства класса (17 МБ).
-Xshare:off
Последовательный порт уборщик мусора имеет минимальный объем памяти за счет более длительного времени паузы во время обработки сборки мусора (см. Алексей Шипилев сравнение ГК на одном фото). Его можно включить с помощью следующего флага. Он может сэкономить до используемого пространства GC (48 МБ).
-XX:+UseSerialGC
Компилятор C2 может быть отключен с помощью следующего флага, чтобы уменьшить данные профилирования, используемые для принятия решения о том, оптимизировать метод или нет.
-XX:+TieredCompilation -XX:TieredStopAtLevel=1
Размер кода уменьшен на 20 МБ. Более того, объем памяти вне JVM уменьшается на 80 МБ (разница между пространством NMT и пространством RSS). Оптимизирующему компилятору C2 требуется 100 МБ.
Компиляторы C1 и C2 можно отключить с помощью следующего флага.
-Xint
Объем памяти вне JVM теперь меньше, чем общее выделенное пространство. Размер кода уменьшен на 43 МБ. Остерегайтесь, это сильно влияет на производительность приложения. Отключение компилятора C1 и C2 уменьшает используемую память на 170 МБ.
Использование Компилятор ВМ Грааль (замена C2) приводит к немного меньшему объему памяти. Он увеличивает на 20 МБ пространство памяти кода и уменьшает на 60 МБ внешнюю память JVM.
Статья Управление памятью Java для JVM предоставляет некоторую важную информацию о различных областях памяти. Oracle предоставляет некоторые подробности в Документация по отслеживанию встроенной памяти. Подробнее об уровне компиляции в расширенная политика компиляции и в отключить C2 уменьшить размер кэша кода в 5 раз. Некоторые подробности о Почему JVM сообщает о большем объеме выделенной памяти, чем размер резидентного набора процесса Linux?, когда оба компилятора отключены.
Куча - это место, где размещаются объекты, однако JVM имеет много других областей памяти, включая разделяемые библиотеки, прямые буферы памяти, стеки потоков, компоненты графического интерфейса, метапространство. Вам нужно посмотреть, насколько большой может быть JVM, и сделать предел достаточно высоким, чтобы вы предпочли, чтобы процесс умер, а не использовал его больше.