Для тех, кто почитал про контейнеры и Docker, но не понимает каким боком к контейнерам прицепилась Java.
Сначала освежим свою память про память Java.
Напомню, что память состоит из Stack и Heap, которые используют RAM (оперативную память) сервера, говоря дальше про память - будем иметь ввиду именно RAM.
А теперь рассмотрим контейнер в разрезе.
Нет конечно, огузок и подбедрышек нас не интересует, мы будем смотреть на структуру памяти в контейнере.
Ее можно разделить на три части:
• Heap Memory – собственно куча;
• Off Heap – это все что не в куче;
• OS Overhead – это накладные расходы памяти, на реализацию процессов внутри контейнера.
Допустим: Мы выделили на контейнер 1 Gb, на рисунке это будет Container Limit – его границы обозначены синим прямоугольником.
На память Java в контейнере мы выделили 80%, то есть 0,8 Gb и Heap занял у нас примерно 80% от контейнера, то есть не много меньше 0,8 Gb, потому что часть ресурсов забрал себе OS Overhead (накладные расходы) на поддержание процессов.
Около 20% контейнера осталось на все что связано не с кучей (Off Heap).
Область Used на рисунке показывает используемую область памяти на выполнение работы приложения.
Теперь надо поговорить о ситуациях, когда память в контейнере может закончиться.
OutOfMemoryError
Если потребление памяти приложением (область Used) достигнет пределов кучи (Heap) мы словим OutOfMemoryError (OOM Error).
Которое говорит, что не хватает место в куче, а именно, в области памяти, в которую помещаются объекты, создаваемые программно в приложении.
Если еще не совсем понятно, то объясню на котиках.
OOM Error - это когда кот, долго-долго орет перед дверями на балкон, а когда эту дверь откроют, то он стоит в проеме и не проходит ни туда ни сюда, часто говорят что котик завис. Если его вовремя подтолкнуть, то он отвиснет и пойдет на балкон по своим котячим делам.
OOM Killer
Это еще одна ситуация, которая может случиться в контейнере, когда заканчивается память
Если программа залезет за пределы контейнера, то получим OutOfMemory Killer (OOM Killer) - это процесс, который завершает приложение, чтобы спасти ядро от сбоя. Он жертвует приложением, чтобы сохранить работу контейнера. Контейнер не факт что упадет, но приложение, которое в нем работает упадет точно.
Такое бывает если оставить потребление памяти Java приложением без контроля.
Опять на кошках.
Если вы решили в субботу подольше поспать и забыли насыпать котикам еды, то OOM Killer цветочкам обеспечен, горшки может и не пострадают, а вот цветы придется сажать заново.
Какая разница между OOM Error и OOM Killer?
OOM Error вы можете обработать и совершить какие-то действия (например: масштабировать приложение), а OOM Killer просто убьет весь процесс.
OOM Killer аналогичен kill-9 (kill минус девять) – команда, которая убивает процесс в Linux.
Все дело в том, что самой популярной реализацией контейнера, является Docker контейнер, который базируется на Linux, даже если запустить его под Windows, все равно ядро будет от Linux.
У Linux нам интересно одно понятие:
CGroups (англ. control group) — механизм ядра Linux, который ограничивает и изолирует вычислительные ресурсы (процессорные, сетевые, ресурсы памяти, ресурсы ввода-вывода) для групп процессов.
Просто говоря, этот механизм позволяет в контейнере управлять ресурсами, в нашем случае памятью.
Как с этим связана Java?
Именно механизмом CGroups Java может зацепиться за память контейнера.
Но это все зависит от версии Java.
Например, Java 7, не умеет пользоваться CGroups, и лимиты контейнера ей неизвестны. По умолчанию maximal heap size = ¼ физической памяти.
Если приложение превысит лимиты контейнера, то будет OOM Killer, а если лимиты контейнера не установлены, то приложение будет забирать память у других приложений на сервере (лимиты лучше поставить, а то лягут все).
Можно, конечно, использовать настройки heap или использовать специальные «образы», которые решают эту проблему, но легче всего использовать правильную версию Java.
Правильные версии начинаются с Java 8 x131 (портирована с Java 9), она начинает понимать CGroups.
А у же в Java 10 появился support для контейнеров: UseContainerSupport, позднее эта функция была портирована в Java 8 x 191.
Или можно просто использовать Java: 11+.
Какой можно сделать вывод:
При использовании памяти контейнера можно получить OutOfMemoryError (OOM Error) или OutOfMemoryKiller (OOM Killer). При первом, приложение сразу не упадет, OOM Error можно поймать, обработать и совершить управляемые действия. Например масштабировать приложение.
Если же случился OOM Killer, то приложение упадет сразу, никаких вариантов по его спасению не останется. И самое не приятное, что внешне сам контейнер будет в порядке, то есть можно даже не подозревать что там что то упало.
Для взаимодействия с памятью контейнера и Java используются Linux механизмы. Но не всякая Java их реализует. Что бы не было проблем для Java 8 нужно использовать версию начиная от 131, а лучше от 191. Или использовать Java: 11+.
Для практики:
OutOfMemoryError: поймай, если сможешь







ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ