Подтвердить что ты не робот

Почему 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 показано:

Native Memory Tracking:

Total: reserved=4023326KB, committed=2762382KB
-                 Java Heap (reserved=1331200KB, committed=1331200KB)
                            (mmap: reserved=1331200KB, committed=1331200KB) 

-                     Class (reserved=1108143KB, committed=64559KB)
                            (classes #8621)
                            (malloc=6319KB #17371) 
                            (mmap: reserved=1101824KB, committed=58240KB) 

-                    Thread (reserved=1190668KB, committed=1190668KB)
                            (thread #1154)
                            (stack: reserved=1185284KB, committed=1185284KB)
                            (malloc=3809KB #5771) 
                            (arena=1575KB #2306)

-                      Code (reserved=255744KB, committed=38384KB)
                            (malloc=6144KB #8858) 
                            (mmap: reserved=249600KB, committed=32240KB) 

-                        GC (reserved=54995KB, committed=54995KB)
                            (malloc=5775KB #217) 
                            (mmap: reserved=49220KB, committed=49220KB) 

-                  Compiler (reserved=267KB, committed=267KB)
                            (malloc=137KB #333) 
                            (arena=131KB #3)

-                  Internal (reserved=65106KB, committed=65106KB)
                            (malloc=65074KB #29652) 
                            (mmap: reserved=32KB, committed=32KB) 

-                    Symbol (reserved=13622KB, committed=13622KB)
                            (malloc=12016KB #128199) 
                            (arena=1606KB #1)

-    Native Memory Tracking (reserved=3361KB, committed=3361KB)
                            (malloc=287KB #3994) 
                            (tracking overhead=3075KB)

-               Arena Chunk (reserved=220KB, committed=220KB)
                            (malloc=220KB) 

Это показывает 2.7 ГБ памяти, в том числе 1,3 ГБ выделенной кучи и почти 1,2 ГБ выделенных стеков потоков (используя много потоков).

Однако при запуске ps ax -o pid,rss | grep <mypid> или top он отображает только 1,6 ГБ резидентной памяти RES/rss. Проверка swap говорит, что никто не использует:

free -m
             total       used       free     shared    buffers     cached
Mem:        129180      99348      29831          0       2689      73024
-/+ buffers/cache:      23633     105546
Swap:        15624          0      15624

Почему JVM указывает, что память 2.7 ГБ зафиксирована, когда только 1,6 ГБ является резидентом? Куда отправились остальные?

4b9b3361

Ответ 1

Я начинаю подозревать, что стековая память (в отличие от кучи JVM), кажется, предварительно загружается, не становясь резидентной, и со временем становится резидентной только до максимальной отметки фактического использования стека.

Да, по крайней мере, в Linux mmap ленив, если не указано иное. Страницы поддерживаются только физической памятью после того, как они записаны (чтения недостаточно из -за оптимизации нулевой страницы)

Память кучи GC эффективно затрагивается копирующим коллектором или предварительным обнулением (-XX:+AlwaysPreTouch), поэтому она всегда будет резидентной. Стеки потоков, otoh не затрагиваются этим.

Для дальнейшего подтверждения вы можете использовать pmap -x <java pid> и перекрестную ссылку на RSS различных диапазонов адресов с выводом из карты виртуальной памяти из NMT.


Зарезервированная память была преобразована в PROT_NONE. Это означает, что диапазоны виртуального адресного пространства имеют записи в структурах ядра vma и, следовательно, не будут использоваться другими вызовами mmap/malloc. Но они по-прежнему будут вызывать ошибки страницы, передаваемые процессу как SIGSEGV, т.е. доступ к ним является ошибкой.

Это важно, чтобы в будущем были доступны смежные диапазоны адресов, что, в свою очередь, упрощает арифметику указателей.

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


Чтобы дать разбивку понятий:

Использованная куча: объем памяти, занятый живыми объектами в соответствии с последним GC

Committed: диапазоны адресов, которые были сопоставлены с чем-то иным, чем PROT_NONE. Они могут или не могут быть подкреплены физическим или подкачки из-за ленивого распределения и подкачки.

Зарезервировано: общий диапазон адресов, который был предварительно отображен через mmap для определенного пула памяти.
Зарезервированное - зафиксированное различие состоит из отображений PROT_NONE, которые гарантированно не будут поддерживаться физической памятью

Резидент: Страницы, которые в настоящее время находятся в физической памяти. Это означает код, стеки, часть выделенных пулов памяти, а также части файлов mmaped, к которым недавно был получен доступ, и выделения вне контроля JVM.

Виртуальный: сумма всех сопоставлений виртуальных адресов. Охватывает выделенные, зарезервированные пулы памяти, а также сопоставленные файлы или общую память. Это число редко бывает информативным, так как JVM может заранее резервировать очень большие диапазоны адресов или отображать большие файлы.