Почему 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.

Другие вопросы по тегам