Почему Sun JVM продолжает потреблять все больше RSS-памяти, даже когда размеры кучи и т. Д. Стабильны? - PullRequest
32 голосов
/ 23 октября 2009

За прошедший год я значительно улучшил использование Java-кучи в своем приложении - значительное сокращение на 66%. Для этого я отслеживал различные метрики, такие как размер кучи Java, процессор, не-куча Java и т. Д. Через SNMP.

Недавно я отслеживал, сколько реальной памяти (RSS, резидентный набор) JVM, и я несколько удивлен. Реальная память, используемая JVM, кажется полностью независимой от размера кучи моих приложений, не-кучи, места на диске, количества потоков и т. Д.

Размер кучи, измеренный Java SNMP График использования кучи Java http://lanai.dietpizza.ch/images/jvm-heap-used.png

Реальная память в КБ. (Например: 1 МБ КБ = 1 ГБ) График использования кучи Java http://lanai.dietpizza.ch/images/jvm-rss.png

(Три провала на графике кучи соответствуют обновлениям / перезапускам приложения.)

Это проблема для меня, потому что вся дополнительная память, которую использует JVM, это «кража» памяти, которую ОС может использовать для кэширования файлов. Фактически, как только значение RSS достигает ~ 2,5-3 ГБ, я начинаю видеть более медленное время отклика и более высокую загрузку ЦП в моем приложении, в основном в ожидании ввода-вывода. Как только начинается переход к разделу подкачки, это очень нежелательно.

Итак, мои вопросы:

  • Почему это происходит? Что происходит "под капотом" ?
  • Что я могу сделать, чтобы контролировать реальное потребление памяти JVM?

кровные детали:

  • RHEL4 64-bit (Linux - 2.6.9-78.0.5.ELsmp # 1 SMP Ср 24 сентября 2008 г. x86_64 ... GNU / Linux)
  • Java 6 (сборка 1.6.0_07-b06)
  • Tomcat 6
  • Приложение (потоковое HTTP-видео по запросу)
    • Высокий ввод / вывод через java.nio FileChannels
    • От сотен до сотен тысяч тем
    • Низкое использование базы данных
    • весна, спящий

Соответствующие параметры JVM:

-Xms128m  
-Xmx640m  
-XX:+UseConcMarkSweepGC  
-XX:+AlwaysActAsServerClassMachine  
-XX:+CMSIncrementalMode    

-XX:+PrintGCDetails 
-XX:+PrintGCTimeStamps  
-XX:+PrintGCApplicationStoppedTime  
-XX:+CMSLoopWarn  
-XX:+HeapDumpOnOutOfMemoryError 

Как я измеряю RSS:

ps x -o command,rss | grep java | grep latest | cut -b 17-

Это входит в текстовый файл и регулярно считывается в базу данных RRD моей системы мониторинга. Обратите внимание, что ps выводит килобайты.


Проблема и решение s :

Хотя в итоге ответ ATorras оказался в конечном итоге правильным, но kdgregory подтолкнул меня к правильному ответу Диагностика пути с использованием pmap. (Идите, проголосуйте за оба их ответа!) Вот что происходит:

Вещи, которые я точно знаю:

  1. Мое приложение записывает и отображает данные с помощью JRobin 1.4 , что я кодировал в своем приложении более трех лет назад.
  2. Самый занятый экземпляр приложения в настоящее время создает
    1. Более 1000 файлов новой базы данных JRobin (по 1,3 МБ каждый) в течение часа после запуска
    2. ~ 100 + каждый день после запуска
  3. Приложение обновляет эти объекты базы данных JRobin каждые 15 секунд, если есть что написать.
  4. В конфигурации по умолчанию JRobin:
    1. использует основанный на java.nio сервер доступа к файлам. Этот бэкэнд отображает MappedByteBuffers на сами файлы.
    2. раз в пять минут поток демона JRobin вызывает MappedByteBuffer.force() для каждой базовой базы данных JRobin MBB
  5. pmap в списке:
    1. 6500 отображений
    2. 5500 из которых составляли 1,3 МБ файлов базы данных JRobin, что составляет ~ 7,1 ГБ

Последним моментом был мой "Эврика!" момент.

Мои корректирующие действия:

  1. Подумайте об обновлении до последней версии JRobinLite 1.5.2, которая явно лучше
  2. Реализация правильной обработки ресурсов в базах данных JRobin. На данный момент, как только мое приложение создает базу данных, а затем никогда не выгружает ее после того, как база данных больше не используется активно.
  3. Поэкспериментируйте с перемещением MappedByteBuffer.force() к событиям обновления базы данных, а не к периодическому таймеру. Волшебная проблема исчезнет?
  4. Немедленно , измените серверную часть JRobin на реализацию java.io - изменение строки строки. Это будет медленнее, но, возможно, это не проблема. Вот график, показывающий непосредственное влияние этого изменения.

График использования памяти Java RSS http://lanai.dietpizza.ch/images/stackoverflow-rss-problem-fixed.png

Вопросы, которые я могу или не могу иметь время выяснить:

  • Что происходит внутри JVM с MappedByteBuffer.force()? Если ничего не изменилось, он все еще записывает весь файл? Часть файла? Он загружается первым?
  • Всегда ли определенное количество MBB всегда присутствует в RSS? (RSS составляла примерно половину от всех выделенных размеров MBB. Совпадение? Я подозреваю, что нет.)
  • Если я переместу MappedByteBuffer.force() к событиям обновления базы данных, а не к периодическому таймеру, проблема волшебным образом исчезнет?
  • Почему уклон RSS был таким регулярным? Он не соответствует ни одной из метрик загрузки приложения.

Ответы [ 4 ]

18 голосов
/ 23 октября 2009

Просто идея: буферы NIO размещены вне JVM.

EDIT: Что касается 2016 года, то стоит рассмотреть комментарий @Lari Hotari [ Почему Sun JVM продолжает потреблять все больше RSS-памяти, даже когда размеры кучи и т. Д. Стабильны? ], поскольку в 2009 году RHEL4 имел glibc <2,10 (~ 2,3) </p>

Привет.

14 голосов
/ 23 октября 2009

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

В твоем случае я не думаю, что это проблема. График показывает 3 мегабайта, а не 3 гигабайта, как вы пишете в тексте. Это действительно мало и вряд ли вызовет подкачку.

Так что еще происходит в вашей системе? Это ситуация, когда у вас много серверов Tomcat, каждый из которых использует 3M RSS? Вы добавляете много флагов GC, они указывают, что процесс проводит большую часть своего времени в GC? У вас есть база данных, работающая на той же машине?

Редактировать в ответ на комментарии

Что касается размера RSS в 3M - да, это казалось слишком низким для процесса Tomcat (я установил свой флажок, и у меня на 89M был один, который некоторое время не был активным). Тем не менее, я не обязательно ожидаю, что он будет> размером кучи, и я, конечно же, не ожидаю, что он будет почти в 5 раз больше размера кучи (вы используете -Xmx640) - в худшем случае это должен быть размер кучи + некоторое для каждого приложения постоянная.

Что заставляет меня подозревать ваши номера. Итак, вместо графика с течением времени, выполните следующую команду, чтобы получить снимок (замените 7429 на любой идентификатор процесса, который вы используете):

ps -p 7429 -o pcpu,cutime,cstime,cmin_flt,cmaj_flt,rss,size,vsize

(отредактируйте Stu, чтобы мы могли отформатировать результаты к вышеприведенному запросу для информации ps:)

[stu@server ~]$ ps -p 12720 -o pcpu,cutime,cstime,cmin_flt,cmaj_flt,rss,size,vsize
%CPU - - - -  RSS SZ  VSZ
28.8 - - - - 3262316 1333832 8725584

Редактировать, чтобы объяснить эти числа для потомков

RSS, как уже отмечалось, это размер резидентного набора: страницы в физической памяти. SZ содержит количество страниц, записываемых процессом (плата за фиксацию); man-страница описывает это значение как «очень грубое». VSZ содержит размер карты виртуальной памяти для процесса: доступные для записи страницы и общие страницы.

Обычно VSZ немного> SZ и очень> RSS. Этот вывод указывает на очень необычную ситуацию.

Уточнение того, почему единственное решение - уменьшить количество объектов

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

и некоторые другие опции

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

3 голосов
/ 23 октября 2009

Почему это происходит? Что происходит "под капотом"?

JVM использует больше памяти, чем просто куча. Например, методы Java, стеки потоков и собственные дескрипторы размещаются в памяти отдельно от кучи, а также внутренних структур данных JVM.

В вашем случае возможными причинами неполадок могут быть: NIO (уже упоминалось), JNI (уже упоминалось), чрезмерное создание потоков.

О JNI, вы писали, что приложение не использует JNI, но ... Какой тип драйвера JDBC вы используете? Может ли это быть тип 2, и протекает? Хотя это очень маловероятно, как вы сказали, использование базы данных было низким.

При избыточном создании потоков каждый поток получает свой собственный стек, который может быть довольно большим. Размер стека фактически зависит от ВМ, ОС и архитектуры, например для JRockit это 256 КБ в Linux x64, я не нашел ссылки в документации Sun по виртуальной машине Sun. Это напрямую влияет на память потока (память потока = размер стека потока * количество потоков). И если вы создаете и уничтожаете много потоков, память, вероятно, не используется повторно.

Что я могу сделать, чтобы контролировать реальное потребление памяти JVM?

Честно говоря, от сотен до сотен тысяч тем мне кажется огромным. Тем не менее, если вам действительно нужно столько потоков, размер стека потоков можно настроить с помощью опции -Xss. Это может уменьшить потребление памяти. Но я не думаю, что это решит всю проблему. Я склонен думать, что где-то есть утечка, когда я смотрю на график реальной памяти.

1 голос
/ 23 октября 2009

Текущий сборщик мусора в Java хорошо известен тем, что не освобождает выделенную память, хотя память больше не требуется. Довольно странно, однако, что ваш размер RSS увеличивается до> 3 ГБ, хотя размер кучи ограничен 640 МБ. Используете ли вы какой-либо собственный код в своем приложении или у вас включен пакет оптимизации производительности для Tomcat? В этом случае у вас, конечно, может быть собственная утечка памяти в вашем коде или в Tomcat.

В Java 6u14 Sun представила новый сборщик мусора «Сначала мусор», который может высвободить память обратно в операционную систему, если она больше не требуется. Он по-прежнему относится к категории экспериментальных и по умолчанию не включен, но если это выполнимый вариант для вас, я постараюсь обновить его до последней версии Java 6 и включить новый сборщик мусора с аргументами командной строки "-XX: + UnlockExperimentalVMOptions - XX: + UseG1GC». Это может решить вашу проблему.

...