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

StackOverflow https://stackoverflow.com/questions/1612939

Вопрос

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

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

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

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

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

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

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

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

Кровавые подробности:

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

Соответствующие параметры 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:

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

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

  1. Мое приложение записывает и отображает данные с Робин 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 из которых составляли файлы базы данных JRobin объемом 1,3 МБ, что составляет ~ 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()?Если ничего не изменилось, по-прежнему ли он записывает весь файл целиком?Часть файла?Загружает ли он его первым?
  • Всегда ли в RSS присутствует определенное количество MBB?(RSS составлял примерно половину от общего объема выделенных МБАЙТ.Совпадение?Я подозреваю, что нет.)
  • Если я переместу MappedByteBuffer.force() для событий обновления базы данных, а не периодического таймера, проблема волшебным образом исчезнет?
  • Почему уклон RSS был таким регулярным?Это не коррелирует ни с одним из показателей загрузки приложения.
Это было полезно?

Решение

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

Редактировать: Согласно 2016 году, стоит рассмотреть комментарий @Lari Hotari [ Почему Sun JVM продолжает потреблять все больше RSS-памяти, даже когда размеры кучи и т.д. стабильны? ] потому что еще в 2009 году у RHEL4 был glibc < 2.10 (~2.3)

С уважением.

Другие советы

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

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

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

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

Что касается размера RSS на 3 М - да, это показалось слишком низким для процесса Tomcat (я установил свой флажок, и у меня есть один на 89 м, который некоторое время не был активен).Однако я не обязательно ожидаю, что это будет > размер кучи, и я, конечно, не ожидаю, что это будет почти в 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 содержит количество страниц, доступных для записи процессом (плата за фиксацию).;страница руководства описывает это значение как "очень приблизительное".VSZ содержит размер карты виртуальной памяти для процесса:доступные для записи страницы плюс общие страницы.

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

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

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

И некоторые другие варианты

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top