Почему JVM сообщает о большей выделенной памяти, чем размер резидентного набора процесса linux?
При запуске приложения Java (в YARN) с включенным отслеживанием собственной памяти (-XX:NativeMemoryTracking=detail
см. https://docs.oracle.com/javase/8/docs/technotes/guides/vm/nmt-8.html и https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr007.html), я могу видеть, сколько памяти использует JVM в разных категориях.
Мое приложение на jdk 1.8.0_45 показывает:
Отслеживание собственной памяти: Итого: зарезервировано = 4023326KB, зафиксировано =2762382KB - Куча Java (зарезервировано = 1331200 КБ, зафиксировано = 1331200 КБ) (карта: зарезервировано = 1331200 КБ, зафиксировано = 1331200 КБ) - Класс (зарезервировано = 1108143 КБ, зафиксировано = 64559 КБ) (занятия № 8621) (malloc = 6319KB # 17371) (карта: зарезервировано = 1101824 КБ, зафиксировано = 58240 КБ) - Тема (зарезервировано = 1190668 КБ, зафиксировано = 1190668 КБ) (нить № 1154) (стек: зарезервировано = 1185284KB, зафиксировано =1185284KB) (malloc=3809KB #5771) (арена = 1575 КБ № 2306) - Код (зарезервировано = 255744 КБ, зафиксировано = 38384 КБ) (malloc=6144KB #8858) (карта: зарезервировано = 249600 КБ, зафиксировано = 32240 КБ) - GC (зарезервировано = 54995 КБ, зафиксировано = 54995 КБ) (malloc=5775KB #217) (mmap: зарезервировано = 49220KB, зафиксировано =49220KB) - Компилятор (зарезервировано = 267 КБ, зафиксировано = 267 КБ) (malloc=137KB #333) (арена = 131 КБ № 3) - Внутренний (зарезервировано = 65106 КБ, зафиксировано = 65106 КБ) (malloc=65074KB #29652) (mmap: зарезервировано = 32 КБ, зафиксировано = 32 КБ) - Символ (зарезервировано = 13622 КБ, зафиксировано = 13622 КБ) (malloc=12016KB #128199) (арена = 1606 КБ № 1) - Отслеживание собственной памяти (зарезервировано = 3361 КБ, зафиксировано = 3361 КБ) (malloc=287KB #3994) (отслеживание накладных расходов = 3075 КБ) - Арена Чанк (зарезервировано = 220 КБ, зафиксировано = 220 КБ) (ТаНос = 220KB)
Это показывает 2,7 ГБ выделенной памяти, включая 1,3 ГБ выделенной кучи и почти 1,2 ГБ выделенных стеков потоков (с использованием множества потоков).
Однако при запуске ps ax -o pid,rss | grep <mypid>
или же top
он показывает только 1,6 ГБ RES/rss
резидентная память. Проверка свопа говорит, что ни один не используется:
бесплатно -m общее количество использованных свободных общих буферов в кеше Память: 129180 99348 29831 0 2689 73024 -/+ буферы / кэш: 23633 105546 Своп: 15624 0 15624
Почему JVM указывает, что 2,7 ГБ памяти выделяется, если только 1,6 ГБ резидентны? Куда делись остальные?
1 ответ
Я начинаю подозревать, что стековая память (в отличие от кучи JVM), кажется, предварительно загружается, не становясь резидентной, и со временем становится резидентной только до максимальной отметки фактического использования стека.
Да, malloc/mmap ленив, если не указано иное. Страницы поддерживаются физической памятью только после того, как к ним осуществляется доступ.
Память кучи GC эффективно соприкасается с копирующим коллектором или предварительно обнулением (-XX:+AlwaysPreTouch
), поэтому он всегда будет резидентом. Стеки потоков, otoh не затрагиваются этим.
Для дальнейшего подтверждения вы можете использовать pmap -x <java pid>
и перекрестную ссылку на RSS различных диапазонов адресов с выводом из карты виртуальной памяти из NMT.
Зарезервированная память была отображена с PROT_NONE
, Это означает, что диапазоны виртуального адресного пространства имеют записи в структурах vma ядра и, следовательно, не будут использоваться другими вызовами mmap/malloc. Но они по-прежнему будут вызывать ошибки страницы, передаваемые процессу как SIGSEGV, то есть доступ к ним является ошибкой.
Это важно, чтобы в будущем были доступны смежные диапазоны адресов, что, в свою очередь, упрощает арифметику указателей.
Зафиксированная, но не зарезервированная память была сопоставлена, например, с PROT_READ | PROT_WRITE
но доступ к нему все еще вызывает ошибку страницы. Но эта ошибка страницы незаметно обрабатывается ядром, поддерживая ее фактической памятью и возвращаясь к выполнению, как будто ничего не произошло.
Т.е. это детали реализации / оптимизации, которые не будут замечены самим процессом.
Чтобы дать разбивку понятий:
Использованная куча: объем памяти, занятый живыми объектами в соответствии с последним GC
Committed: диапазоны адресов, которые были сопоставлены с чем-то иным, чем PROT_NONE. Они могут или не могут быть подкреплены физическим или обменом из-за ленивого распределения и подкачки.
Зарезервировано: общий диапазон адресов, который был предварительно отображен через mmap
для определенного пула памяти.
Зарезервированная - зафиксированная разница состоит из PROT_NONE
сопоставления, которые гарантированно не поддерживаются физической памятью
Резидент: Страницы, которые в настоящее время находятся в физической памяти. Это означает код, стеки, часть выделенных пулов памяти, а также части файлов mmaped, к которым недавно был получен доступ, и выделения вне контроля JVM.
Виртуальный: сумма всех сопоставлений виртуальных адресов. Охватывает выделенные, зарезервированные пулы памяти, а также сопоставленные файлы или общую память. Это число редко бывает информативным, поскольку JVM может заранее резервировать очень большие диапазоны адресов или создавать большие файлы mmap.