Использование памяти 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 и дал следующий результат:
Таким образом, вы можете видеть, что CXF использует меньше памяти, и он более стабилен (он не растет все время как ref.impl.).
Наконец, я обнаружил проблему с отслеживателем проблем JDK - https://bugs.openjdk.java.net/browse/JDK-8074108 - снова об утечках памяти в zip-библиотеке, и проблема еще не закрыта. Таким образом, похоже, что я не могу устранить проблему с утечками памяти в своем приложении, просто могу сделать некоторые обходные пути.
Спасибо всем за вашу помощь!
Ответы
Ответ 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)?