Объем памяти Java растет неограниченно, но MemoryMXBean сообщает о стабильной куче и размере не кучи

Я работаю с командой, разрабатывающей приложение Java GUI, работающее на целевой системе Linux на 1 ГБ.

У нас есть проблема, когда память, используемая нашим Java-процессом, растет бесконечно, пока Linux, наконец, не уничтожит процесс Java.

Наша память о куче здоровая и стабильная. (мы подробно профилировали нашу кучу). Мы также использовали MemoryMXBean для мониторинга использования памяти без использования кучи, поскольку мы полагали, что проблема может быть там. Тем не менее, мы видим, что сообщаемый размер кучи + сообщил, что размер без кучи остается стабильным.

Вот пример того, как номера могут выглядеть при запуске приложения в нашей целевой системе с 1 ГБ ОЗУ (куча и не куча, о которых сообщает MemoryMXBean, общая память, используемая процессом Java, контролируется с использованием верхней команды Linux (резидентной памяти)):

При запуске:

  • 200 мегабайт выделенной кучи
  • 40 МБ, но не куча
  • 320 МБ, используемый java-процессом

Через 1 день:

  • 200 мегабайт выделенной кучи
  • 40 МБ, но не куча
  • 360 МБ, используемый java-процессом

Через 2 дня:

  • 200 мегабайт выделенной кучи
  • 40 МБ, но не куча
  • 400 МБ, используемый java-процессом

Цифры выше - это просто "более чистое" представление о том, как работает наша система, но они довольно точны и близки к реальности. Как вы можете видеть, тренд ясен. После нескольких недель работы приложения система Linux начинает испытывать проблемы из-за нехватки системной памяти. Вещи начинают замедляться. Через несколько часов процесс Java будет убит.

После нескольких месяцев профилирования и попыток понять это, мы все еще не в порядке. Я чувствую, что трудно найти информацию об этой проблеме, так как большинство обсуждений объясняют кучу или другие пулы памяти без кучи. (например, Metaspace и т.д.)

Мои вопросы таковы:

  • Если вы сломаете это, что включает память, используемая в java-процессе? (в дополнение к пулам памяти кучи и без кучи)

  • Какие другие потенциальные источники существуют для утечек памяти? (нативный код? Накладные расходы JVM?) Какие из них, в общем, наиболее вероятные преступники?

  • Как можно контролировать/профилировать эту память? Все, что находится за пределами кучи + без кучи, в настоящее время является для меня черным ящиком.

Любая помощь будет принята с благодарностью.

Ответ 1

Я попытаюсь частично ответить на ваш вопрос.

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

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

Вы можете попытаться профилировать память. Посмотрите на корни GC и узнайте, какие из них являются JNI глобальными ссылками. Это может помочь вам узнать, какие классы не могут быть собраны. Например, это общая проблема в awt, которая может потребовать явного удаления компонентов.

Чтобы проверить использование внутренней памяти JVM (которая не принадлежит памяти кучи/неактивной памяти) -XX:NativeMemoryTracking, очень удобно. Он позволяет отслеживать размеры стека потоков, накладные расходы gc/компилятора и многое другое. Самое главное в том, что вы можете создать базовую линию в любой момент времени, а затем отслеживать различия в памяти с момента создания базовой линии.

# jcmd <pid> VM.native_memory baseline
# jcmd <pid> VM.native_memory summary.diff scale=MB

Total:  reserved=664624KB  -20610KB, committed=254344KB -20610KB
...

Вы также можете использовать команду JMX com.sun.management:type=DiagnosticCommand/vmNativeMemory для создания этих отчетов.

И... Вы можете пойти глубже и проверить pmap -x <pid> и/или procfs.

Ответ 2

Наконец, мы, по-видимому, определили основную причину проблемы. Это ответ на то, что конкретно вызвало эту проблему, поскольку это маловероятно, что это может быть полезно для других.

TL;DR:

Проблема была вызвана ошибкой в ​​JDK, которая теперь исправлена ​​и будет транслироваться с помощью JDK 8u152. Ссылка на отчет об ошибке

Вся история:

Мы продолжали следить за производительностью памяти приложений после того, как я впервые разместил этот вопрос, а метод XX: NativeMemoryTracking, предложенный vsminkov, значительно помог сузить и определить область в памяти, которая протекала.

Мы обнаружили, что область "Протектор - Аренас" растет бесконечно. Поскольку такая утечка была чем-то, что мы были уверены, что мы не сталкивались раньше, мы начали тестирование с более ранними версиями java, чтобы узнать, было ли это введено в какой-то конкретной точке.

После возврата к java 8u73 утечки не было, и хотя принуждение использовать более старую версию JDK не было оптимальным, по крайней мере у нас был способ обойти проблему на данный момент.

Несколько недель спустя, во время работы с обновлением 73, мы заметили, что приложение все еще протекает, и мы снова начали искать виновника. Мы обнаружили, что проблема теперь находится в области Class - malloc.

На этом этапе мы почти наверняка обнаружили, что утечка не была нашей ошибкой в ​​приложении, и мы рассматривали возможность обращения к Oracle, чтобы сообщить о проблеме как потенциальной ошибке, но затем мой коллега наткнулся на этот отчет об ошибке в компиляторе JDK hotspot: Ссылка на отчет об ошибке

Описание ошибки очень похоже на то, что мы видели. Согласно тому, что написано в отчете, утечка памяти присутствует с момента выпуска java 8, и после тестирования с ранним выпуском JDK 8u152 мы теперь уверены, что утечка исправлена. По прошествии 5 дней работы область видимости нашей прикладной памяти теперь кажется почти стабильной на 100%. Область malloc класса все еще немного растет, но сейчас она растет со скоростью около 100 КБ в день (по сравнению с несколькими МБ ранее), и, протестировав только на 5 дней, я не могу сказать наверняка, что она не стабилизируется в конечном итоге.

Я не могу сказать наверняка, но, похоже, проблемы, связанные с ростом класса malloc и Thread arenas, были связаны. В любой момент обе проблемы исчезли в обновлении 152. К сожалению, обновление, похоже, не запланировано для официального выпуска до конца 2017 года, но наше тестирование с ранним выпуском кажется многообещающим до сих пор.