Почему Sun JVM продолжает потреблять все больше RSS-памяти, даже когда размер кучи и т. д. стабилен?
за последний год я сделал огромные улучшения в использовании кучи Java моего приложения-твердое сокращение 66%. В погоне за этим я отслеживал различные показатели, такие как размер кучи Java, cpu, Java non-heap и т. д. через SNMP.
недавно я отслеживал, сколько реальной памяти (RSS, резидентный набор) JVM и несколько удивлен. Реальная память, потребляемая JVM, кажется полностью независимой от размера кучи моих приложений, не кучи, пространства eden, количества потоков, так далее.
размер кучи, измеренный 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-3GB, я начинаю видеть более медленное время отклика и более высокую загрузку процессора из моего приложения, в основном ожидание ввода-вывода. Как какой-то момент подкачки к разделу подкачки срабатывает. Все это очень нежелательно.
Итак, мои вопросы:
- почему это происходит? Что происходит "под капотом"?
- что я могу сделать, чтобы контролировать реальное потребление памяти JVM?
кровавые подробности:
- rhel4 64-бит (Linux-2.6.9-78.0.5.ELsmp #1 SMP Ср 24 сентября ... 2008 архитектуру x86_64 ... GNU / Linux)
- Java 6 (build 1.6.0_07-b06)
- Tomcat 6
- применение (по требованию http потокового видео)
- высокий I / O через java.НИО 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
. (Иди, проголосуй за оба их ответа!) Вот что происходило:
вещи, которые я знаю наверняка:
- мое приложение записывает и отображает данные с JRobin 1.4, что-то, что я закодировал в свое приложение более трех лет назад.
- самый загруженный экземпляр приложения в настоящее время создает
- более 1000 несколько новых файлов базы данных JRobin (около 1,3 МБ каждый) в течение часа после запуска
- ~100+ каждый день после пуска
- приложение обновляет эти объекты базы данных JRobin один раз в 15 секунд, если есть что писать.
- по умолчанию конфигурация JRobin:
- использует
java.nio
-доступ к файлам на основе back-end. Это back-end картыMappedByteBuffers
к самим файлам. - каждые пять минут поток демона JRobin вызывает
MappedByteBuffer.force()
на каждом jrobin базовой базы данных MBB
- использует
-
pmap
перечислены:- отображений
- 5500 из которых были 1.3 MB jrobin файлы базы данных, который работает до ~7.1 GB
что последним пунктом был мой "Эврика!" момент.
мои действия:
- рассмотреть вопрос об обновлении до последней JRobinLite 1.5.2, который явно лучше
- реализовать надлежащую обработку ресурсов в базах данных JRobin. На данный момент, как только мое приложение создает базу данных, а затем никогда не сбрасывает ее после того, как база данных больше не используется.
- эксперимент с перемещением
MappedByteBuffer.force()
для события обновления базы данных, а не периодический таймер. Исчезнет ли проблема волшебным образом? - тут, измените серверную часть JRobin на java.реализация ввода-вывода--изменение линии линии. Это будет медленнее, но, возможно, это не проблема. Вот график, показывающий непосредственное влияние этого изменения.
вопросы, которые я могу или не могу успеть выяснить:
- что происходит внутри JVM с
MappedByteBuffer.force()
? Если ничего не изменилось, он по-прежнему записывает весь файл? Часть файла? Он загружает его первым? - есть ли определенное количество MBB всегда в RSS в любое время? (RSS был примерно наполовину выделен Размеры MBB. Совпадение? Подозреваю, что нет.)
- если я сдвину
MappedByteBuffer.force()
к событиям обновления базы данных, а не периодическому таймеру, проблема волшебным образом исчезнет? - почему наклон RSS был таким регулярным? Он не коррелирует ни с одной из метрик загрузки приложения.
4 ответов
просто идея: буферы NIO размещаются вне JVM.
EDIT: В 2016 году стоит рассмотреть комментарий @Lari Hotari [ почему Sun JVM продолжает потреблять все больше RSS-памяти, даже когда размер кучи и т. д. стабилен?] потому что еще в 2009 году RHEL4 имел glibc
с уважением.
RSS представляет страницы, которые активно используются -- для Java это в первую очередь живые объекты в куче и внутренние структуры данных в JVM. Вы мало что можете сделать, чтобы уменьшить его размер, кроме как использовать меньше объектов или делать меньше обработки.
в вашем случае, я не думаю, что это проблема. На графике показано потребление 3 мегабайт, а не 3 гигабайта, как вы пишете в тексте. Это действительно мало и вряд ли вызовет пейджинг.
Итак, что еще происходит в твоем организме? Это ситуация, когда у вас есть много серверов Tomcat, каждый из которых потребляет 3M RSS? Вы бросаете много флагов GC, они указывают, что процесс проводит большую часть своего времени в GC? У вас есть база данных, работающая на одном компьютере?
редактировать в ответ на комментарии
Что касается размера 3M RSS - да, это казалось слишком низким для процесса Tomcat (я проверил свою коробку, и у меня есть один на 89M, который не был активен некоторое время). Однако Я не обязательно ожидать, что это будет > размер кучи, и я, конечно, не ожидаю, что он будет почти в 5 раз больше размера кучи (вы используете-Xmx640)-в худшем случае это должен быть размер кучи + некоторая константа для приложения.
что заставляет меня подозревать своего номера. Таким образом, вместо графика с течением времени, пожалуйста, запустите следующее, Чтобы получить снимок (замените 7429 на любой идентификатор процесса, который вы используете):
ps -p 7429 -o pcpu,cutime,cstime,cmin_flt,cmaj_flt,rss,size,vsize
(отредактируйте Stu, чтобы мы могли сформировать результаты по вышеуказанному запросу для ps info:)
[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
Edit, чтобы объяснить эти цифры для потомков
RSS, как уже отмечалось, - это размер резидентного набора: страницы в физической памяти. SZ содержит количество страниц, записываемых процессом (заряд фиксации); manpage описывает это значение как "очень грубое". VSZ содержит размер карты виртуальной памяти для процесса: записываемые страницы плюс общие страницы.
обычно VSZ немного > SZ и очень много > RSS. Этот выход указывает на очень необычную ситуацию.
разработка о том, почему единственным решением является сокращение объектов
RSS представляет количество страниц, находящихся в ОЗУ -- страницы, к которым активно обращаются. С Java сборщик мусора будет периодически ходить по всему графу объектов. Если этот объектный граф занимает большую часть пространства кучи, коллектор будет касаться каждой страницы в куче, требуя, чтобы все эти страницы стали резидентами памяти. GC очень хорошо сжимать кучу после каждой основной коллекции, поэтому, если вы работаете с частичной кучей, большая часть страниц не должна быть в ОЗУ.
и некоторые другие варианты
Я заметил, что вы упомянули о наличии сотен до низких тысяч потоков. Стеки для этих потоков также будут добавлены в RSS, хотя это не должно быть много. Предполагая, что потоки имеют небольшую глубину вызова( типичную для потоков обработчика app-server), каждый должен только потреблять одну-две страницы из физической памяти, хотя там половина-Мэг фиксации заряда для каждого.
почему это происходит? Что происходит "под капотом"?
JVM использует больше памяти, чем просто кучи. Например, методы Java, стеки потоков и собственные дескрипторы выделяются в памяти отдельно от кучи, а также внутренние структуры данных JVM.
в вашем случае возможными причинами проблем могут быть: NIO (уже упоминалось), JNI (уже упоминалось), чрезмерное создание потоков.
о JNI, вы написали, что приложение не использовал JNI но... Какой тип драйвера вы используете? Может быть, это тип 2 и утечка? Это очень маловероятно, хотя, как вы сказали, использование базы данных было низким.
о чрезмерном создании потоков, каждый поток получает свой собственный стек, который может быть довольно большим. Размер стека фактически зависит от виртуальной машины, ОС и архитектуры, например, для виртуальная машина JRockit это 256K на Linux x64, я не нашел ссылку в документации Sun для виртуальной машины Sun. Это влияет непосредственно на память потока (память потоков = размер стека потоков * количество потоков). И если вы создаете и уничтожаете много потоков, память, вероятно, не используется повторно.
что я могу сделать, чтобы контролировать реальное потребление памяти JVM?
честно говоря, сотни до низких тысяч потоков кажутся мне огромными. Тем не менее, если вам действительно нужно столько потоков, размер стека потоков можно настроить с помощью . Это может уменьшить потребление памяти. Но я не думаю, это решит всю проблему. Я склонен думать, что где-то есть утечка, когда я смотрю на реальный график памяти.
текущий сборщик мусора в Java хорошо известен тем, что не освобождает выделенную память, хотя память больше не требуется. Однако довольно странно, что размер RSS увеличивается до >3 ГБ, хотя размер кучи ограничен 640 МБ. Вы используете какой-либо собственный код в своем приложении или у вас включен собственный пакет оптимизации производительности для Tomcat? В этом случае у вас может быть утечка собственной памяти в вашем коде или в Tomcat.
С Java 6u14, Sun представила новый сборщик мусора" мусор-первый", который способен освободить память обратно в операционную систему, если это больше не требуется. Он по-прежнему классифицируется как экспериментальный и не включен по умолчанию, но если это осуществимый вариант для вас, я бы попытался перейти на новейший выпуск Java 6 и включить новый сборщик мусора с аргументами командной строки "- XX:+UnlockExperimentalVMOptions-XX:+UseG1GC". Это может решить вашу проблему.