Подтвердить что ты не робот

Использование памяти Java-процессов постоянно увеличивается

Предпосылки:

  • ПК с 16 ГБ оперативной памяти
  • JDK 1.8.x установлен на Ubuntu 16.10 x64.
  • стандартное веб-приложение на основе Spring, которое развертывается на Tomcat 8.5.x. Tomcat настроен со следующими параметрами: CATALINA_OPTS="$CATALINA_OPTS -Xms128m -Xmx512m -XX:NewSize=64m -XX:MaxNewSize=128m -Xss512k -XX:+UseParallelGC -XX:+AggressiveOpts -XX:+UseFastAccessorMethods -XX:MaxMetaspaceSize=512m -XX:-TieredCompilation -XX:ReservedCodeCacheSize=512m"
  • JMeter 2.13 для запуска нагрузочных тестов
  • JProfiler 9.x для отслеживания использования памяти кучи java
  • top использовать для отслеживания использования памяти процесса Java

Когда я запускаю тесты нагрузки последовательно 3 раза, я наблюдаю (используя top), что java-процесс увеличивает количество используемой памяти:

  • После запуска Tomcat он использует ~ 1Gb
  • после первого запуска теста он использует 4.5Gb
  • Когда все тесты завершены, Tomcat использует 7 ГБ оперативной памяти.

Все это время размер кучи ограничен, и JProfiler подтверждает, что размер кучи не превышает 512 МБ.

Это скриншот JProfiler. Красные цифры внизу - это размер памяти, используемый java-процессом (согласно top). введите описание изображения здесь

Вопрос: почему процесс Java продолжает увеличивать использование памяти во время работы?

Спасибо!

UPD # 1: О возможном дублировании: они have confirmed that this only happens on Solaris., но я использую Ubuntu 16.10. Кроме того, у остроконечного вопроса нет ответа, который бы объяснил причину проблемы.

UPD # 2: мне пришлось вернуться к этому вопросу после некоторой паузы. И теперь я использую pmap util для создания дампа памяти, используемого процессом java. У меня есть три дампа: до запуска тестов, после выполнения первых тестов и после некоторых N тестов. Тесты приносят много трафика в приложение. Все дампы находятся здесь: https://gist.github.com/proshin-roman/752cea2dc25cde64b30514ed9ed9bbd0. Они довольно огромные, но самые интересные вещи находятся на 8-й линии с размером кучи: перед тестированием он занимает 282.272 Kb и 3.036.400 Kb - более 10 раз! И он растет каждый раз, когда я запускаю тесты. В то же время размер кучи является постоянным (согласно JProfiler/VisualVM). Какие варианты я должен найти для причины этой проблемы? Debug JVM? Я попытался найти способы "взглянуть" на этот сегмент памяти, но не смог. Итак:

  • Можно ли каким-либо образом определить сегмент памяти [heap]?
  • Ожидается ли такое поведение java?

Я буду благодарен за любые советы по этой проблеме. Спасибо всем!

UPD # 3: используя jemalloc (спасибо @ivan за идею), я получил следующее изображение: введите описание изображения здесь

И похоже, что у меня почти такая же проблема, как описано здесь: http://www.evanjones.ca/java-native-leak-bug.html

UPD # 4: на данный момент я обнаружил, что проблема связана с java.util.zip.Inflater/Deflater, и эти классы используются во многих местах моего приложения. Но наибольшее влияние на потребление памяти делает взаимодействие с удаленным SOAP-сервисом. В моем приложении используется эталонная реализация стандарта JAX-WS, и он дал следующее потребление памяти при загрузке (он имеет низкую точность после 10Gb): потребление памяти с эталонной реализацией Затем я выполнил те же тесты нагрузки, но с реализацией Apache CXF и дал следующий результат: потребление памяти с Apache CXF Таким образом, вы можете видеть, что CXF использует меньше памяти, и он более стабилен (он не растет все время как ref.impl.). Наконец, я обнаружил проблему с отслеживателем проблем JDK - https://bugs.openjdk.java.net/browse/JDK-8074108 - снова об утечках памяти в zip-библиотеке, и проблема еще не закрыта. Таким образом, похоже, что я не могу устранить проблему с утечками памяти в своем приложении, просто могу сделать некоторые обходные пути.

Спасибо всем за вашу помощь!

4b9b3361

Ответ 1

Моя гипотеза заключается в том, что вы собираете информацию о рассылке/стеки вызовов и т.д. в JProfiler, а наблюдаемый вами рост RSS связан с тем, что JProfiler сохраняет эти данные в памяти.

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

В прошлом у меня был похожий случай.

Ответ 2

Можете ли вы повторить свои тесты с помощью этой опции -XX:MaxDirectMemorySize=1024m? Точное значение этого предела не имеет значения, но оно показывает возможные "утечки".

Можете ли вы также предоставить детали GC (-XX:+PrintGC)?

java.nio.ByteBuffer является возможной причиной из-за его конкретной финализации.

ОБНОВЛЕНИЕ # 1

Я видел подобное поведение по двум другим причинам: java.misc.Unsafe(маловероятно) и высоконагруженные JNI-вызовы.

Трудно понять без профиля теста.

ОБНОВЛЕНИЕ # 2

Оба высоконагруженных метода JNI-вызовов и finalize() вызывают описанную проблему, поскольку у объектов не хватает времени для завершения.

Ниже фрагмент j.u.zip.Inflater:

/**
 * Closes the decompressor when garbage is collected.
 */
protected void finalize() {
    end();
}

/**
 * Closes the decompressor and discards any unprocessed input.
 * This method should be called when the decompressor is no longer
 * being used, but will also be called automatically by the finalize()
 * method. Once this method is called, the behavior of the Inflater
 * object is undefined.
 */
public void end() {
    synchronized (zsRef) {
        long addr = zsRef.address();
        zsRef.clear();
        if (addr != 0) {
            end(addr);
            buf = null;
        }
    }
}

private native static void end(long addr);

Ответ 3

На основе бритвы Occam: не могло быть, что у вас где-то есть утечка памяти (т.е. "непреднамеренные удержания объектов" a'la Effective Java Item 6)?