?

Log in

No account? Create an account

Java жрёт память как не в себя? Ну, не совсем Java и не совсем в себя

« previous entry | next entry »
ноя. 29, 2018 | 05:07 pm

Дано: долгоживущее серверное приложение на Java (https://github.com/bozaro/git-as-svn). Приложеньке выдано 12GB под Java-объекты (-Xmx12g). Сверху ожидается некоторый оверхед от инфраструктурных вещей которые JVM обеспечивает приложению (сборка мусора, компиляция байткода в натив, етц). Ну не знаю, 10-20% кажется разумным. Также есть сервер с Ubuntu 14.04 о 24 ядрах на котором запущено это приложение под Oracle JDK 1.8u45.

Далее запускаем приложение и видим что оно под нагрузкой улетает хорошо за 40GB резидентной памяти (колонка RES в top(1)) и в какой-то момент за ним приходит OOM killer. 40GB, Карл, при -Xmx12g!

"Течёт память" подумали мужики и пошли чесать репу.

1. Первым делом была обновлена JVM 1.8u45 -> 1.8u181 (наисвежайшая на тот момент). Помогло никак.

2. Почитали логи GC. Всё чинно-мирно, внутри хипа полно свободного места и ограничитель в 12GB на месте.

3. "Память жрёт что-то за пределами heap'а" подумали мужики. Нашли статью про поиск утечек в DirectBuffer'ах при помощи jxray. Сняли хип-дамп, насчитали несколько десятков MB памяти аллоцированной DirectBuffer'ами. Мало, не то.

4. "Память жрёт что-то в других потрохах JVM". Нашли механизм Native Memory Tracking, который позволяет отслеживать на что JVM потратила память за пределами хипа. Выяснили что в момент когда процесс суммарно занимает 20GB, Native Memory Tracking может нам рассказать куда потрачено 14.8GB из них (это, конечно, не 10-20% оверхеда, а вполне себе 25%). Где ещё 5GB неясно.

5. "Память ТЕЧЁТ?" Нашли статью про поиск утечек в нативном коде JVM через компиляцию jemalloc с включенной профилировкой, запуском JVM под этим jemalloc и вдумчивым курением логов. Можно, но как-то сложно и грустно. Запомнили мысль и пошли дальше, искать ключи под фонарём.

6. "Память жрёт что-то за пределами JVM". Каким-то невероятным чудом нашли багрепорт в другом Java-проекте (presto) который тоже испытывал проблемы с неудержимым потреблением памяти. И открылось замечательное. glibc берёт память у ядра большими кусками и очень нехотя отдаёт их обратно. Причём количество "недоотданной памяти" зависит от количества ядер в системе, размеров порций которыми приложение делает malloc и чёрт знает чего ещё. Получить внятного ответа на вопрос "сколько максимум памяти может занимать одна арена" пока не вышло. Больше всего страдают приложения с большим количеством потоков которое аллоцирует/деаллоцирует память большими порциями. Как бы то ни было, уменьшение MALLOC_ARENA_MAX с дефолтных 16 до 4 привело к тому что свыше 20GB приложение расти перестало.

7. Попутно было обнаружено что в приложении живёт (в основном, естественно, спит) порядка 100 потоков:

30 порождённых самим приложением
5 от JVM C1 compiler
9 от JVM C2 compiler
24 "gang worker" для сборщика мусора G1
20 "G1 Concurrent Refinement Thread" для сборщика мусора G1

Тут в общем-то возникает справедливый WTF - зачем сборщику мусора потоков почти вдвое больше чем ядер в системе. Внятного ответа пока нет, зато нашёлся баг в JVM по которому складывается впечатлнение что столько много "G1 Concurrent Refinement Thread" не нужно и вообще баг. Нужно ли столько gang worker'ов тоже неясно. И нужно ли C2 compiler'у?

8. Финальное решение: уменьшить MALLOC_ARENA_MAX до единицы. Уменьшить количество "G1 Concurrent Refinement Thread". Уменьшить количество потоков запускаемых самим приложением (примерно вдвое, причём совершенно безболезненно для самого приложения). Вообще кажется так что MALLOC_ARENA_MAX будет доставлять боль многим большим долгоживущим многопоточным серверным приложениям.

Мораль: раньше надо было тюнить сборщик мусора и количество одновременно открытых файловых дескрипторов, а теперь ещё и glibc :(

Бонусные ссылки по теме:



Кажется, это самый масштабный продолб оперативы вникуда который мне когда-либо встречался.
Метки: ,

Ссылка | Оставить комментарий |

Comments {3}

Andy Melnikov

(без темы)

from: nponeccop
date: ноя. 29, 2018 08:34 pm (UTC)
Ссылка

Ничё, скоро ядро тюнить надо будет. Хорошо ещё, что Б.Г. избавил нас от джамперов.

В винде вон я читаю истории одна прохладнее другой, о том как народ борется учится жить с Control Guard Flow. Короче в MS изобрели очередную вариацию на тему ASLR - там винда с целью борьбы с эксплоитами строит таблицы допустимых косвенных переходов, которые едят память. Ну и так сделано, что таблицы эти нельзя деаллоцировать. В результате страдают JIT-движки (в-основном жабоскрипт), которые часто эти таблицы меняют ну и соответственно течёт память этих таблиц, которой, как в вашем случае, "нигде не видно".

Ответить | Ветвь дискуссии

Марат Радченко

(без темы)

from: slonopotamus
date: ноя. 29, 2018 08:59 pm (UTC)
Ссылка

В винде вообще ад в плане левого функционала который система суёт в приложения. Консольный хелловорлд на плюсах запускает по потоку на ядро!

Ответить | Уровень выше | Ветвь дискуссии

Andy Melnikov

(без темы)

from: nponeccop
date: ноя. 29, 2018 09:30 pm (UTC)
Ссылка

При балмере такого не было!

Ответить | Уровень выше | Ветвь дискуссии