Как стать автором
Обновить

Комментарии 24

Спасибо за статью!
Отличная статья, спасибо большое!
Поправьте -XX+AlwaysActAsServerClassMachine на -XX:+AlwaysActAsServerClassMachine. И переменная окружения должна быть MALLOC_ARENA_MAX вместо MALLOC_ARENAS_MAX.

Вот это внимание к деталям! Спасибо, поправил.

Есть ли смысл в MALLOC_ARENA_MAX>1 для JVM?

Безусловно есть, чем больше арен, тем меньше конкуренции за них при вызове malloc. Но это с точки зрения теории, на практике всё зависит от приложения. Для наших типовых сервисов сокращение числа арен до 4 не повлияло никак на их производительность. Больше мы не эксперименировали, т.к. jemalloc зашёл лучше.

Вы вот вроде бы что-то ответили, но что — я так и не понял. Смысл есть, но зависит от приложения, но вы не пробовали.

Попробую развернуть)
Чем меньше арен, тем больше contention при использовании malloc. Теоретически это может привести к деградации производительности (тут всё зависит от приложения — насколько интенсивно в нём используется malloc).
На наших типовых сервисах мы не заметили никакого проседания при уменьшении числа арен с 640 (8 * 80) до 4. Ещё меньше ставить не пробовали, так как стали повсеместно внедрять jemalloc.

Насколько я понимаю, если контейнеры на основе Alpine, то в muslc MALLOC_ARENA_MAX переменная роли не играет?

Очень интересная статья, но есть множество спорных моментов.
Вы отключаете overcommit памяти в Ось?
Если нет, то по сути большинство ограничений не уменьшают потребление памяти, если помнить о «ленивом» выделении памяти линуксом. Даже commited memory в NMT не отображает потребление памяти, а лишь максимальное, если jvm ее затребует. Поэтому, большинством этих настроек просто промениваете OOMKiller на OutOfMemoryError, StackOverFlow и т.д. Не знаю, есть ли такая цель, мне кажется общее ограничение дополнительно дает возможность для перераспределения памяти между областями если понадобится (overcommit).
1.-XX:ThreadStackSize ограничивает максимальный размер стэка потока, если вашим потоком столько не нужно, память не расходуется по 1мб на стэк, минимум от 228кб, то того размера который нужен. Уменьшение параметра потребление RSS не уменьшит
2. MALLOC_ARENA_MAX аналогично, в вашей же ссылке на сайт ibm какой-то бред, считать превышение vsz над rss мемори ликом и чинить это, хотя как раз и написано, что они оптимизируют virtual memory size, вы тоже оптимизируете vsz? Непонятно тогда откуда у вас график по уменьшению rss, наверняка это не rss, так как настройка влияет только на virtual memory, на RSS не влияет.
3. Code cache аналогично, если вы знаете что 240мб у вас не потребляется, то значит и памяти столько не используется. Ограничить можно, думаю что вытеснение кода при переполнении все таки менее болезненно, чем OOMKiller, но вы же хотите делать ExitOnFullCodeCache, значит вас это не волновало.
4. MaxMetaSpace аналогично, потребляется столько сколько нужно. Ставя настройку вы просто ограничиваете потенциальную утечку этим разделом, чтобы получить OutOfMemoryError, а не OOMKiller

В вашей статье из 4 ссылок 3 про virtual memory size и в одной про native memory leak. Да выходит если есть memory leak, то уменьшение кол-ва арен зажмет утечку в меньших рамках, это все объясняет, согласен, и если не удается починить утечку, то нужно уменьшать,
Конкретно в нашем случае никакой утечки не было. Но пиковое необходимое количество памяти превышало имеющуюся в наличии оперативку.
Спасибо за вопросы, попробую ответить по пунктам.

1. Честно говоря, не припомню, чтобы документация описывала ThreadStackSize как максимальный размер стека. Пруфа в виде ссылки на код у меня, к сожалению, под рукой нет, однако практика показывает, что объём закоммиченной под пул Threads памяти (по показаниям native memory tracking) уменьшается с уменьшением -XX:ThreadStackSize, независимое подтверждение есть у Алексея Шипилева в JVM Anatomy Quark #12: Native Memory Tracking.

2. На графике изображён именно RSS (метрика, которую сообщает cgroups). Большое число арен (которое масштабируется по числу цпу на хосте) ведёт к неоправданной фрагментации памяти, естественно это отражается на RSS.

3 и 4. Да, вы правы, от ограничения CodeCache и Metaspace JVM не начнёт потреблять меньше памяти, поэтому они в разделе «ограничения», а не «оптимизации». Для нас основной вопрос был в том, есть ли смысл разрешать приложению потреблять 240 Мб, когда ему больше 32 не нужно. Так что речь тут речь в основном о рациональном использовании ресурсов (лимиты в cgroups можем выставить поменьше и знать точно, сколько каких контейнеров поместится в хост-машину). Ну и получить Java-ООМ приятнее, чем системный — не надо лишний раз гадать что израсходовало память (для полного счастья не хватает ExitOnFullCodeCache).
1. commited memory != RSS, при дефолтных настройках vm.overcommit_memory,
vm.overcommit_ratio на linux.
Я немного не о том. JVM (насколько я понимаю этот механизм) при создании треда вызывает pthread_attr_setstacksize со значением, высчитанным как раз из ThreadStackSize.

pthread_attr_setstacksize задаёт минимальный размер стека (он же становится максимальным для не-main треда): man7.org/linux/man-pages/man3/pthread_attr_setstacksize.3.html

Спасибо за статью. Спорные моменты конечно есть, но однозначно годится в качестве отправной точки в оптимизации размеров контейнеров с java-приложениями.

Если не трудно — можете озвучить? (понятно, что многое зависит от конкретных сервисов, профиля нагрузки, я старался по это оговаривать) Возможно смогу более развёрнуто что-то изложить.
Наш опыт показывает, что Java в Docker — это не только удобно, но и в итоге довольно экономично. Надо только научиться их готовить.

С учётом того, что до этого у вас был kvm, а теперь Docker, в чём именно вышла экономия? Вы подходили с реальными цифрами (например, что с той же самой нагрузкой справляется парк машин на 20% серверов меньше и т.п.)? Ну или требуется меньше дискового пространства на 40%? Я про реальные цифры. Это было бы очень интересно узнать.
Передал этот вопрос нашей команде эксплуатации:
Экономия по оперативной памяти — в kvm виртуалке свое ядро, система и стек приложений, на это 500-1000MB памяти надо закладывать, а это примерно двойной оверхед для мелких сервисов. Плюс контейнер может отдать память в систему сразу, а виртуалка только после рестарта (kvm baloon у нас приводил к проблемам).

По объёму и пропускной способности диска ещё выиграли — на каждую виртуалку раньше выделяли образ минимум по 10GB, c учётом того что у нас до 30 контейнеров на хост — это уже значительный оверхед на пустом месте, потому что большая часть железа работает в блейдах, где максимум 2 диска 2.5". И в нехватку дискового пространства гораздо чаще бы упирались — предсказать сколько места понадобится заранее не всегда возможно.

Производительность дисковой подсистемы с kvm отличается в разы от хостовой с некоторыми драйверами, до 200% оверхеда спокойно могло быть в некоторых случаях.

Ну и оверхед управления этим всем — рулить 1000 виртуалок вместо 1000 контейнеров с учётом прочих задач — это ещё наверное пару человек пришлось бы нанимать (тот же ресайз образа виртуалки это не самая быстрая задача, + ковыряние с srv-iov и настройками на каждой виртуалке). Релизы бы стали медленнее, если бы каждый плейбук еще системные конфиги на машины приносил, как было раньше
Большое спасибо за ответ! Особенно за развёрнутую информацию.
Разве -XX:+UseContainerSupport есть теперь и Java 8? Вроде бы там вот такой аналог этой опции -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap.
Появилась в 8u191 в октябре 2018
Я так понимаю, что это должно всех JVM касаться, включая ту, которая в OpenJDK?
Зарегистрируйтесь на Хабре, чтобы оставить комментарий