Java-программа с 16 ГБ виртуальной памяти и ростом: это проблема?

В Mac OSX 5.8 у меня есть программа Java, которая работает на 100% ЦП в течение очень долгого времени - несколько дней или более (это программа проверки модели, анализирующая параллельную программу, так что это более или менее ожидаемо). Однако размер его виртуальной памяти, как показано в мониторе активности OSX, через день или около того становится огромным: сейчас он составляет 16 ГБ и продолжает расти. Использование физической памяти примерно стабильно на уровне 1,1 ГБ или около того.

Я хотел бы знать: является ли 16 ГБ (и рост) признаком проблем, которые могут замедлять мою программу?

I start the program with "java -Xmx1024m -ea"

java version "1.6.0_24"
Java(TM) SE Runtime Environment (build 1.6.0_24-b07-334-9M3326)
Java HotSpot(TM) 64-Bit Server VM (build 19.1-b02-334, mixed mode)

Спасибо всем за их предложения. Я попробую предложения по профилированию, данные в некоторых ответах, и вернусь (это может занять некоторое время из-за многодневного времени работы).

В ответ на некоторые из приведенных ниже пунктов средство проверки моделей почти не выполняет ввод-вывод (только операторы печати, в зависимости от настроек отладки). В том режиме, который я использую, у него нет графического интерфейса. Я не являюсь основным автором средства проверки модели (хотя я работал над некоторыми его внутренними компонентами), но я не верю, что оно использует JNI. не делает никакого отображения памяти. Кроме того, я не прошу JVM Oracle/Sun создавать множество потоков (см. объяснение ниже).

Дополнительная виртуальная память не привела к остановке средства проверки моделей, но, судя по частоте вывода на печать, оно постепенно работает все медленнее по мере увеличения использования виртуальной памяти. (Возможно, это просто из-за того, что все больше и больше сборщиков мусора.) Я планирую попробовать это на компьютере с Windows в понедельник, чтобы увидеть, возникает ли та же проблема.

Небольшое дополнительное объяснение: программа проверки моделей, которую я запускаю (JPF), сама по себе является почти полной JVM (полностью написанной на Java), которая работает под JVM Oracle/Sun. Конечно, JPF как виртуальная машина узкоспециализирована для поддержки проверки моделей.

Это немного противоречит здравому смыслу, но это означает, что хотя программа, которую я проверяю, предназначена для многопоточности, с точки зрения JVM Sun существует только один поток: тот, который выполняет JPF. JPF эмулирует потоки, необходимые моей программе, как часть процесса проверки модели.


Я считаю, что Стивен С точно определил проблему; Роланд Иллиг дал мне инструменты для проверки. Я ошибался в использовании JNI. Сам JPF не использует JNI, но позволяет использовать плагины, а JNI использовался одним из настроенных плагинов. К счастью, есть эквивалентные плагины, которые я могу использовать на чистой Java. Предварительное использование одного из них не показывает роста виртуальной памяти за последние несколько часов. Спасибо всем за помощь.


person David    schedule 05.06.2011    source источник


Ответы (6)


Подозреваю, что это тоже утечка. Но это не может быть утечка «нормальной» памяти, потому что опция -Xmx1024m ограничивает обычную кучу. Точно так же это не будет утечка кучи «permgen», потому что максимальный размер permgen по умолчанию мал.

Поэтому я подозреваю, что это одно из следующего:

  • Вы пропускаете потоки; то есть потоки создаются, но не завершаются. Они могут быть не активны, но у каждого потока есть сегмент стека (от 256 КБ до 1 МБ по умолчанию... в зависимости от платформы), который не выделен в обычной куче.

  • Вы пропускаете файлы прямого сопоставления. Они сопоставляются с сегментами памяти, выделенными ОС за пределами обычной кучи. (@bestsss предлагает вам искать дескрипторы просочившихся ZIP-файлов, что, я думаю, будет частным случаем этого.)

  • Вы используете некоторый код JNI/JNA, в котором происходит утечка распределенной памяти или что-то подобное.

В любом случае, профилировщик памяти, скорее всего, изолирует проблему или, по крайней мере, устранит некоторые возможности.


Утечка памяти JVM также возможна, но неразумно начинать подозревать JVM до тех пор, пока вы окончательно не устраните возможные причины в своем собственном коде и библиотеках/приложениях, которые вы используете.

person Stephen C    schedule 05.06.2011
comment
Откуда вы взяли размер стека по умолчанию в 1 МБ? Я почти уверен, что где-то читал 256 КБ, но это вполне может зависеть от версии. - person Michael Borgwardt; 05.06.2011
comment
[Спарк: 512; Solaris x86: 320 (было 256 в 5.0 и более ранних версиях); 64-разрядная версия Sparc: 1024; Linux amd64: 1024 (было 0 в 5.0 и более ранних версиях).] - oracle.com/technetwork/java/javase/tech/ - person Stephen C; 05.06.2011
comment
Происходит утечка буферов с прямым отображением. Максимальный объем памяти для прямых буферов. -XX:MaxDirectMemorySize (или sun.nio.MaxDirectMemorySize), если не указано иное, это Runtime.getRuntime().maxMemory() Я бы сделал ставку на файлы с отображением памяти, поскольку они занимают только VirtualMemorySpace и на них не распространяется это ограничение. Также iirc - проблема может заключаться в открытии многих zip-файлов (на самом деле это мой первый выбор сейчас) - person bestsss; 06.06.2011

Поскольку ваше приложение не является приложением реального времени, вы можете сделать следующее:

jps -v

Получите идентификатор процесса из этой таблицы и сохраните его как pid.

jmap -histo $pid > before-gc.hgr
jmap -histo:live $pid > after-gc.hgr
jstack -v $pid > threads.txt

Файл threads.txt говорит вам, что процесс делает в данный момент.

Гистограммы использования кучи before-gc.hgr и after-gc.hgr показывают, сколько памяти может освободить полная сборка мусора.

Может быть, вы получите некоторые намеки на то, что происходит.

person Roland Illig    schedule 05.06.2011

Правильно ли вы NULL делаете все ссылки после того, как закончите с объектами? Интересно, это простая утечка памяти...

Я видел такое поведение в программах на C, которые смешивают большое количество выделений и освобождений для объектов разного размера. Вы можете получить страницы памяти, которые используются наполовину, но не имеют дыр достаточно больших, которые можно было бы использовать для удовлетворения новых запросов памяти.

Это не только вызывает подкачку, но и разрушает локальность ссылок с течением времени — подкачка становится хуже, а затем намного хуже.

Решение в программах на C обычно заключается в переключении на распределитель плит и сохранение объектов разных размеров на разные страницы памяти. Больше никаких отверстий нечетных размеров, и всегда есть указатель на пробел нужного размера для любого вашего объекта.

Конечно, сделать это в управляемой среде, такой как Java, может быть сложно. Можно было бы надеяться, что JVM уже делает это. (Учитывая, что управляемые среды могут обновлять ссылки на новые области памяти и периодически переупаковывать, даже простой подход к распределению памяти должен предотвратить появление глупых дыр.)

Указание на наличие проблемы обычно исходит от трафика подкачки. Если вы видите большой трафик подкачки в своей системе, даже когда RSS (размер резидентного набора) процесса остается стабильным, то вы можете выполнять тонны операций ввода-вывода на диск для операций, которые кажутся полностью выполняемыми в памяти. В Linux и некоторых других системах Unix информацию о трафике подкачки можно найти с помощью команды vmstat 1:

$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa
 0  0      0 727392 351128 3846980    0    0    13    16   41   73  3  1 96  0
 0  0      0 727392 351128 3846988    0    0     0     0 1267 4742  5  1 95  0
...

Столбцы si и so показывают блоков в секунду. (Ну, какой бы временной интервал вы ни выбрали с помощью vmstat 1, vmstat 2 и так далее.)

Если трафик подкачки низкий, то вам, вероятно, не о чем беспокоиться. Если трафик подкачки высок, вам обязательно нужно попытаться выяснить, почему вы выполняете подкачку. Это требует больше работы. :)

person sarnold    schedule 05.06.2011
comment
Явное обнуление ссылок обычно не требуется и считается плохой практикой. - person Michael Borgwardt; 05.06.2011
comment
@Michael, вау, многое изменилось за десять лет с тех пор, как я в последний раз занимался Java. Как вы указываете виртуальной машине, что закончили со ссылкой на объект? - person sarnold; 05.06.2011
comment
@sarnold: обычно за счет того, что он выходит за рамки и следит за тем, чтобы он не хранился в каких-либо долгоживущих коллекциях (обычный виновник утечек памяти). Явное задание ссылки на null было бы полезно только в том случае, если ссылка остается в области видимости еще долго после того, как объект больше не нужен, что является довольно редким случаем. - person Michael Borgwardt; 05.06.2011
comment
@Michael Майкл, ну, конечно, позволить вещам выйти за рамки работает хорошо :) Я полагаю, что в наши дни с улучшенными предоставленными контейнерами люди больше не пишут вручную долгоживущий код коллекции? Кажется, это все, чем мы занимались в школе... - person sarnold; 05.06.2011
comment
@sarnold: о, долгоживущие коллекции по-прежнему являются частой причиной утечек памяти. Но я бы не назвал удаление чего-либо из коллекции обнулением ссылки, хотя, конечно, это часто происходит где-то внутри коллекции. - person Michael Borgwardt; 05.06.2011
comment
Куча Java не увеличивается, так что это не утечка памяти в программе Java. - person Thorbjørn Ravn Andersen; 05.06.2011
comment
@Michael, @Thorbjørn: мне удалить свой ответ как неконструктивный или оставить его, потому что ваши комментарии могут помочь другим в будущем? - person sarnold; 05.06.2011
comment
Сохраните его, в нем действительно есть полезные знания, даже если они могут быть неприменимы в данном случае. - person Michael Borgwardt; 05.06.2011

Для меня это определенно звучит как утечка памяти. Вам следует исследовать с помощью профилировщика кучи, такого как VisualVM (который поставляется с вашим JDK).

Редактировать: я упустил ваш предел кучи. Так что на самом деле это не может быть простой утечкой памяти Java (хотя может быть так, что по какой-то причине размер кучи игнорируется - все же стоит проверить). Другая возможность заключается в том, что ваше приложение удерживает большое количество ресурсов памяти, не относящихся к куче, таких как одноранговые узлы собственного графического интерфейса (окна, которые скрыты, но не депонированы), потоки (объекты потоков малы, но имеют большой объем памяти стека, который может никогда не освобождаться, если поток, например, создан, но не запущен) или IO (не знаю, как это может занимать столько памяти до исчерпания дескрипторов файлов). На более глубоком уровне это может быть проблема выделения памяти, как писал Сарнольд, или утечка памяти внутри самой JVM.

person Michael Borgwardt    schedule 05.06.2011
comment
даже при утечке памяти виртуальная машина должна максимально увеличить пространство кучи до 1024 МБ с учетом параметра -Xmx, верно? Вопрос в том, на что указывает (растущая) виртуальная память. Это просто количество виртуальных адресов, выделенных процессу? - person Dilum Ranatunga; 05.06.2011
comment
@Dilum: ой, ты прав, я совершенно не заметил этого. Это действительно странно. - person Michael Borgwardt; 05.06.2011

Если jvisualvm не сообщает о чрезмерном использовании памяти, это превращается в проблему приложения OS X, где приложением является JVM.

Я предлагаю вам открыть такой вопрос вместо этого.

person Thorbjørn Ravn Andersen    schedule 05.06.2011

Вероятно, невозможно точно ответить на этот вопрос, не зная точно, что делает программа. Может быть, это действительно использует много памяти...

В любом случае вы можете попробовать подключиться к процессу с помощью инструмента VisualVM и посмотреть, что происходит с выделением памяти и освобождение.

Кроме того, существует довольно много -XX: options для отладки сборки мусора они могут предоставить еще больше информации.

person Jeremiah    schedule 05.06.2011