За последний год я сделал огромные улучшения в моем применении Java-кучи - твердое 66% -ное сокращение. Для этого я отслеживал различные показатели, такие как размер кучи Java, процессор, Java-кучу и т.д. Через SNMP.
Недавно я отслеживал, сколько реальной памяти (RSS, резидентный набор) JVM и я несколько удивлен. Реальная память, потребляемая JVM, кажется совершенно независимой от моего размера кучи приложений, не-кучи, пространства eden, количества потоков и т.д.
Размер кучи, измеренный 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?
Детали gory:
- RHEL4 64-bit (Linux - 2.6.9-78.0.5.ELsmp # 1 SMP Wed Sep 24... 2008 x86_64... GNU/Linux)
- Java 6 (сборка 1.6.0_07-b06)
- Tomcat 6
- Приложение (потоковое видео HTTP по запросу)
- Высокий ввод-вывод через java.nio FileChannels
- Сотни до низких тысяч потоков
- Низкое использование базы данных
- Spring, Hibernate
Соответствующие параметры 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 выводит Kilo Bytes.
Проблема и решения:
В конце концов, это был ATorras ответ, который оказался в конечном счете правильным, kdgregory, который направил меня к правильному пути диагностики с использованием pmap
. (Проголосуйте за оба ответа!) Вот что происходит:
То, что я точно знаю:
- Мои приложения записывают и отображают данные с JRobin 1.4, что я закодировал в своем приложении более трех лет назад.
- Самый загруженный экземпляр приложения в настоящее время создает
- Более 1000 новых файлов базы данных JRobin (около 1,3 МБ каждый) в течение часа после запуска
- ~ 100 + каждый день после запуска
- Приложение обновляет эти объекты базы данных JRobin один раз каждые 15 секунд, если есть что писать.
- В конфигурации по умолчанию JRobin:
- использует back-end для доступа к файлу
java.nio
. Этот back-end отображаетMappedByteBuffers
на сами файлы. - раз в пять минут поток демона JRobin вызывает
MappedByteBuffer.force()
на каждой базовой базе данных JRobin MBB
- использует back-end для доступа к файлу
-
pmap
:- 6500 сопоставлений
- 5500 из которых - файлы базы данных JRobin объемом 1,3 МБ, которые работают до ~ 7.1 ГБ.
Этот последний момент был моей "Эврика!" момент.
Мои корректирующие действия:
- Рассмотрим обновление до последней версии JRobinLite 1.5.2, которая, по-видимому, лучше
- Реализовать правильную обработку ресурсов в базах данных JRobin. На данный момент, когда мое приложение создает базу данных, а затем никогда не выгружает ее после того, как база данных больше не используется активно.
- Экспериментируйте с перемещением событий
MappedByteBuffer.force()
к обновлению базы данных, а не периодическим таймером. Будет ли проблема волшебно уходить? - Непосредственно измените исходный код JRobin на реализацию java.io - изменение строки. Это будет медленнее, но это, возможно, не проблема. Вот график, показывающий непосредственное влияние этого изменения.
Вопросы, которые я могу или не могу успеть выяснить:
- Что происходит внутри JVM с помощью
MappedByteBuffer.force()
? Если ничего не изменилось, все равно записывает весь файл? Часть файла? Загружает ли он его первым? - Всегда есть ли в MBB определенное количество MBB? (RSS был примерно наполовину общим распределенным размером MBB. Совпадение? Я подозреваю, что нет.)
- Если я переведу
MappedByteBuffer.force()
на события обновления базы данных, а не на периодический таймер, проблема будет волнующе исчезнуть? - Почему RSS-склон был настолько регулярным? Он не коррелирует с какой-либо из показателей нагрузки приложения.