Por que uma JVM relata mais memória confirmada que o tamanho do conjunto residente do processo Linux?



memory yarn (1)

Ao executar um aplicativo Java (no YARN) com o rastreamento de memória nativa ativado ( -XX:NativeMemoryTracking=detail consulte https://docs.oracle.com/javase/8/docs/technotes/guides/vm/nmt-8.html e https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr007.html ), posso ver quanta memória a JVM está usando em diferentes categorias.

Meu aplicativo no jdk 1.8.0_45 mostra:

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) 

Isso mostra 2,7 GB de memória confirmada, incluindo 1,3 GB de heap alocado e quase 1,2 GB de pilhas de encadeamentos alocadas (usando muitos encadeamentos).

No entanto, ao executar ps ax -o pid,rss | grep <mypid> ps ax -o pid,rss | grep <mypid> ou top , mostra apenas 1,6 GB de memória res. RES/rss . A verificação de swap diz que nenhum está em uso:

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

Por que a JVM indica que 2,7 GB de memória estão comprometidos quando apenas 1,6 GB é residente? Para onde foi o resto?

https://src-bin.com


Answer #1

Estou começando a suspeitar que a memória da pilha (diferente da pilha da JVM) parece ter sido pré-confirmada sem se tornar residente e, com o tempo, se torna residente apenas até a marca d'água máxima do uso real da pilha.

Sim, pelo menos no linux mmap é preguiçoso, a menos que seja dito o contrário. As páginas são suportadas apenas pela memória física depois de gravadas (as leituras não são suficientes devido à otimização de página zero )

A memória heap do GC é tocada com eficácia pelo coletor de cópias ou pelo pré-zeramento ( -XX:+AlwaysPreTouch ), para que seja sempre residente. As pilhas de threads não são afetadas por isso.

Para confirmação adicional, você pode usar pmap -x <java pid> e fazer referência cruzada do RSS de vários intervalos de endereços com a saída do mapa de memória virtual do NMT.

A memória reservada foi mapeada com PROT_NONE . O que significa que os intervalos do espaço de endereço virtual têm entradas nas estruturas vma do kernel e, portanto, não serão usados ​​por outras chamadas mmap / malloc. Mas eles ainda farão com que as falhas de página sejam encaminhadas ao processo como SIGSEGV, ou seja, acessá-las é um erro.

É importante ter intervalos de endereços contíguos disponíveis para uso futuro, o que simplifica a aritmética dos ponteiros.

A memória PROT_READ | PROT_WRITE mas não suportada pelo armazenamento foi mapeada com - por exemplo - PROT_READ | PROT_WRITE PROT_READ | PROT_WRITE mas acessá-lo ainda causa uma falha na página. Mas essa falha de página é silenciosamente tratada pelo kernel, fazendo o backup com memória real e retornando à execução como se nada tivesse acontecido.
Ou seja, é um detalhe / otimização da implementação que não será percebido pelo próprio processo.

Para dar uma descrição detalhada dos conceitos:

Pilha usada : a quantidade de memória ocupada por objetos ativos de acordo com o último GC

Confirmado: intervalos de endereços que foram mapeados com algo diferente de PROT_NONE. Eles podem ou não ter respaldo físico ou troca devido a alocação e paginação preguiçosas.

Reservado : o intervalo total de endereços pré-mapeados via mmap para um conjunto de memórias específico.
A diferença reservada confirmada consiste em mapeamentos PROT_NONE , que garantem que não sejam suportados pela memória física

Residente : páginas que estão atualmente em memória RAM. Isso significa código, pilhas, parte dos conjuntos de memórias confirmadas, mas também partes de arquivos mmaped que foram acessados ​​recentemente e alocações fora do controle da JVM.

Virtual : a soma de todos os mapeamentos de endereços virtuais. Abrange conjuntos de memórias reservadas e confirmadas, mas também arquivos mapeados ou memória compartilhada. Esse número raramente é informativo, pois a JVM pode reservar intervalos de endereços muito grandes com antecedência ou arquivos grandes de mmap.





yarn