Марат Радченко (slonopotamus) wrote,
Марат Радченко
slonopotamus

Category:

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

Дано: долгоживущее серверное приложение на 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 :(

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



Кажется, это самый масштабный продолб оперативы вникуда который мне когда-либо встречался.
Tags: java, linux
Subscribe
  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 3 comments