{"id":2175,"url":"\/distributions\/2175\/click?bit=1&hash=803b6e1bcbd9dfc4ba9456fda887a878c80d24df8d3a575913b14876e18923a5","title":"TJ \u0437\u0430\u043a\u0440\u043e\u0435\u0442\u0441\u044f 10 \u0441\u0435\u043d\u0442\u044f\u0431\u0440\u044f \u2014\u00a0\u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0439\u0442\u0435 \u0430\u043d\u043e\u043d\u0441 \u0441 \u0434\u0435\u0442\u0430\u043b\u044f\u043c\u0438","buttonText":"\u0427\u0438\u0442\u0430\u0442\u044c","imageUuid":"d1d355d8-93a3-5140-aeae-14b03046b760","isPaidAndBannersEnabled":false}
Разработка
140k

Java внутри Docker: что нужно знать, чтобы не было проблем

На моем последнем проекте я столкнулся с проблемой: приложения Java внутри Linux Docker-контейнеров потребляли очень много памяти. Например, некоторые из наших микросервисов (на основе Spring Boot) внутри контейнера могли использовать до двух гигабайт памяти, тогда как при обычном запуске вне контейнера показатель был в районе 200-300 мегабайт.

Ситуация, когда java внутри контейнера съедает очень много памяти

После недолгого гугления оказалось, что когда мы запускаем приложения как "java -jar mypplication-fat.jar", то JVM по умолчанию приспосабливается к среде выполнения, в которой она работает и таким образом обеспечивается максимально возможное быстродействие java-программ.

CGroups и старые приложения

В Docker-контейнерах, как и в виртуальных машинах (Virtual Machines, VM), можно разграничивать ресурсы, выделять количество ядер и виртуальной памяти. При этом контейнеры обладают отличной изоляцией ресурсов (CPU, память, файловые системы), и один контейнер совершенно ничего не знает о других. Таким образом контейнеры не мешают работе друг друга, и мы можем запускать десятки контейнеров с разными переменными средами (environment) и конфигурациями. Такая изоляция возможно только благодаря возможности ядра Linux — cgroups.

Однако некоторые из приложений были разработаны задолго до появления cgroups. Такие инструменты, как 'top', 'ps', 'free', а также и JVM, не оптимизированы для эффективной работы в контейнерах.

Ограничим память

Первое, что может прийти на ум — это ограничить размер самого Docker-контейнера. Создадим простое приложение на Spring Boot и запустим контейнер, поставив ограничение памяти 150 мегабайт. Вот пример команды на хосте с одним гигабайтом памяти:

$ docker run -it --rm --name mycontainer150 -p 8080:8080 -m 150M rafabene/java-container:openjdk

В приложении заранее был создан rest-метод, который загружает память до отказа объектами типа String. Вызовем его:

$ curl http://`docker-machine ip docker1024`:8080/api/memory

Вызванный метод возвращает что-то вроде "Выделено более чем 80% памяти (219.8 MiB) из максимально доступных JVM (241.7 MiB) ".

Но отсюда два вопроса:

1. Почему максимальный размер для JVM 241 мегабайт?

2. Если мы выше указали, что контейнеру доступно только 150 мегабайт памяти, то почему приложение потребляет почти 220?

Первое, что нужно помнить о максимальном размере кучи (max heap size) у JVM, это то, что размер по умолчанию будет занимать 1/4 памяти. И второе — когда мы при запуске контейнера выше указывали флаг "-m 150M", то докер зарезервировал 150 мегабайт в оперативной памяти и 150 мегабайт в Swap. Поэтому-то приложение и не рухнуло и отработало как нужно. Но при этом вы должны понимать, что увеличение памяти в контейнере — это не решение проблемы. Всегда может случиться ситуация, когда Java-приложение может выйти за рамки лимита, и тогда процесс будет убит.

Решение проблемы

Решений проблем может быть несколько, но мы поговорим о лучшем, на мой взгляд, способе.

Начиная с JDK 8u131+ и JDK 9 нам дали возможность использовать экспериментальные возможности JVM, которые позволяют нам обращаться к возможностям вышеупомянутых CGroups. Одна из этих возможностей — это настроить JVM так, чтобы она автоматически определяла максимальное возможное потребление памяти с помощью опции -XX:+UseCGroupMemoryLimitForHeap. Попробуем:

$ docker run -d --name mycontainer8g-jdk9 -p 8080:8080 -m 600M rafabene/java-container:openjdk-cgroup $ docker logs mycontainer8g-jdk9|grep MaxHeapSize size_t MaxHeapSize = 157286400 {product} {ergonomic}

JVM прочитала, что максимум доступно памяти 600 мегабайт, и создала JVM с максимальным размером памяти примерно 150 мегабайт. Ровно четверть, как и говорилось выше.

Ну и как пример, мое приложение на работе без ограничений использовало 1.7 гигабайта памяти, после того, как я использовал экспериментальные фичи и ограничил размер контейнера до 512 мегабайт — 331 мегабайт.

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

Материал подготовлен и частично переведен при помощи статьи с developers.readhat.com

0
15 комментариев
Написать комментарий...
Просто Хэнк
Ответить
Развернуть ветку
Твиттерский Жук
приложения Java потребляли очень много памяти

Так и должно быть)

Ответить
Развернуть ветку
sf

мне кажется такой контент на хабр лучше. не? 

Ответить
Развернуть ветку
140k
Автор

мне просто было пофиг, скучно, поддержал мертвый раздел на tj

Ответить
Развернуть ветку
sf

имхо он мертворожденный. если было бы что-то околопрограмминга, то может и зашло бы

Ответить
Развернуть ветку
Твиттерский Жук

Можно мне, как ламеру, сказать, чем отличаются докеры от каких-нибудь флетпаков и снапов?

Ответить
Развернуть ветку
Аккаунт удален

Комментарий недоступен

Ответить
Развернуть ветку
Аккаунт удален

Комментарий недоступен

Ответить
Развернуть ветку
Pavel Osadchuk

тем что он везде 

и я трахаюсь с ним уже третий день пытаясь развернуть cockpit cms на azure web app

Ответить
Развернуть ветку
Slow Dimon

Как я понял, снапы работают и взаимодействуют с хостом напрямую, имеют доступы к памяти, фс и прочие плюшки. В то время как контейнеры сами в себе- работают в докере, ограничены во всем, довольствуются только своей песочницей.

Ответить
Развернуть ветку
Аккаунт удален

Комментарий недоступен

Ответить
Развернуть ветку
Slow Dimon

Ну я упрощенно.

Ответить
Развернуть ветку
красивый папуг

на линухе уже давно контейнеры нативными стали, изолирован лишь контекст.

а ресурсы и возможности - общие

Ответить
Развернуть ветку
NiemandDa

А кто чистый докер в продакшене юзает?

Ответить
Развернуть ветку
140k
Автор

это не продакшен, кто сказал такое?

Ответить
Развернуть ветку
Читать все 15 комментариев
null