Perché una JVM segnala più memoria impegnata rispetto alla dimensione impostata residente del processo linux?

Quando si esegue un’app Java (in YARN) con tracciamento memoria nativa abilitata ( -XX:NativeMemoryTracking=detail consultare 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 vedere quanta memoria la JVM sta usando in diverse categorie.

La mia app su jdk 1.8.0_45 mostra:

 Tracciamento nativo della memoria:

 Totale: riservato = 4023326 KB, impegnato = 2762382 KB
 - Java Heap (riservato = 1331200 KB, commit = 1331200 KB)
                             (mmap: riservato = 1331200 KB, commit = 1331200 KB) 

 - Classe (riservato = 1108143KB, commit = 64559KB)
                             (classi # 8621)
                             (malloc = 6319KB # 17371) 
                             (mmap: riservato = 1101824KB, commit = 58240KB) 

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

 - Codice (riservato = 255744 KB, commit = 38384 KB)
                             (malloc = 6144 KB # 8858) 
                             (mmap: riservato = 249600 KB, impegnato = 32240 KB) 

 - GC (riservato = 54995 KB, impegnato = 54995 KB)
                             (malloc = 5775 KB # 217) 
                             (mmap: riservato = 49220 KB, impegnato = 49220 KB) 

 - Compilatore (riservato = 267 KB, impegnato = 267 KB)
                             (malloc = 137 KB # 333) 
                             (arena = 131KB # 3)

 - Interno (riservato = 65106 KB, impegnato = 65106 KB)
                             (malloc = 65074 KB # 29652) 
                             (mmap: riservato = 32 KB, impegnato = 32 KB) 

 - Simbolo (riservato = 13622 KB, impegnato = 13622 KB)
                             (malloc = 12016 KB # 128199) 
                             (arena = 1606 KB # 1)

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

 - Arena Chunk (riservato = 220 KB, impegnato = 220 KB)
                             (Malloc = 220KB) 

Questo mostra 2,7 GB di memoria impegnata, tra cui 1,3 GB di heap allocato e quasi 1,2 GB di stack thread allocati (utilizzando molti thread).

Tuttavia, quando si esegue ps ax -o pid,rss | grep ps ax -o pid,rss | grep o in top mostra solo 1,6 GB di memoria res RES/rss residente. Il controllo dello scambio non dice nessuno in uso:

 libero -m
              memorizzato nella cache i buffer condivisi gratuiti totali utilizzati
 Mem: 129180 99348 29831 0 2689 73024
 - / + buffer / cache: 23633 105546
 Swap: 15624 0 15624

Perché la JVM indica che la memoria da 2,7 GB è impegnata quando risiede solo 1,6 GB? Dove sono finiti gli altri?

Comincio a sospettare che la memoria dello stack (a differenza dell’heap JVM) sembra essere precommessa senza diventare residente e nel tempo diventa residente solo fino al limite massimo dell’utilizzo effettivo dello stack.

Sì, malloc / mmap è pigro a meno che non sia specificato diversamente. Le pagine sono supportate solo dalla memoria fisica dopo l’accesso.

La memoria heap GC viene effettivamente toccata dal raccoglitore di copie o dal pre-azzeramento ( -XX:+AlwaysPreTouch ), quindi sarà sempre residente. Le pile di thread otoh non sono interessate da questo.

Per ulteriori conferme è ansible utilizzare pmap -x e rimandare l’RSS di vari intervalli di indirizzi con l’output dalla mappa della memoria virtuale da NMT.


La memoria riservata è stata salvata con PROT_NONE . Ciò significa che gli intervalli di spazio di indirizzi virtuali hanno voci nelle strutture vma del kernel e quindi non saranno utilizzati da altre chiamate mmap / malloc. Ma continueranno a causare errori di pagina inoltrati al processo come SIGSEGV, vale a dire che accedervi è un errore.

È importante disporre di intervalli di indirizzi contigui disponibili per uso futuro, che a loro volta semplificano l’aritmetica del puntatore.

La memoria con memoria non PROT_READ | PROT_WRITE ma non memorizzata è stata mappata con, ad esempio, PROT_READ | PROT_WRITE PROT_READ | PROT_WRITE ma accedervi causa ancora un errore di pagina. Ma quell’errore di pagina viene gestito silenziosamente dal kernel eseguendolo con la memoria effettiva e ritornando all’esecuzione come se nulla fosse accaduto.
Cioè è un dettaglio di implementazione / ottimizzazione che non sarà notato dal processo stesso.


Per dare una ripartizione dei concetti:

Heap utilizzato : la quantità di memoria occupata dagli oggetti live in base all’ultimo GC

Committed : intervalli di indirizzi mappati con qualcosa di diverso da PROT_NONE. Possono o non possono essere supportati da fisici o swap a causa di allocazioni pigre e paging.

Riservato : l’intervallo di indirizzi totale che è stato pre-mappato tramite mmap per un particolare pool di memoria.
La differenza impegnata riservata è costituita PROT_NONE mapping PROT_NONE , che sono garantiti per non essere supportati dalla memoria fisica

Residente : pagine che sono attualmente nella RAM fisica. Ciò significa codice, stack, parte dei pool di memoria impegnati, ma anche porzioni di file mmaped che sono stati recentemente consultati e allocazioni al di fuori del controllo della JVM.

Virtuale : la sum di tutti i mapping di indirizzi virtuali. Copertine impegnate, pool di memoria riservati ma anche file mappati o memoria condivisa. Questo numero è raramente informativo in quanto la JVM può riservare in anticipo intervalli di indirizzi molto ampi o file grandi di mmap.