web-dev-qa-db-fra.com

Pourquoi une JVM signale-t-elle plus de mémoire engagée que la taille de l'ensemble de résidents du processus Linux?

Lors de l'exécution d'une application Java (dans YARN) avec le suivi de la mémoire native activé (-XX:NativeMemoryTracking=detail voir https://docs.Oracle.com/javase/8/docs/technotes/guides/vm/nmt-8.html et https://docs.Oracle. com/javase/8/docs/technotes/guides/troubleshoot/tooldescr007.html ), je peux voir combien de mémoire la JVM utilise dans différentes catégories.

Mon application sur jdk 1.8.0_45 montre:

 Suivi de la mémoire native: 
 
 Total: réservé = 4023326 Ko, engagé = 2762382 Ko 
 - Java Heap (réservé = 1331200 Ko, commis = 1331200 Ko) 
 (mmap: réservé = 1331200 Ko, engagé = 1331200 Ko) 
 
 - Classe (réservé = 1108143 Ko, engagé = 64559 Ko) 
 (classes # 8621 ) 
 (malloc = 6319 Ko # 17371) 
 (mmap: réservé = 1101824 Ko, engagé = 58240 Ko) 
 
 - Fil (réservé = 1190668 Ko, engagé = 1190668 Ko) 
 (thread # 1154) 
 (pile: réservé = 1185284KB, engagé = 1185284KB) 
 (malloc = 3809KB # 5771) 
 (arena = 1575KB # 2306) 
 
 - Code (réservé = 255744KB, engagé = 38384KB) 
 (Malloc = 614 4KB # 8858) 
 (Mmap: réservé = 249600KB, engagé = 32240KB) 
 
 - GC (réservé = 54995KB, engagé = 54995KB) 
 (Malloc = 5775KB # 217) 
 (Mmap: réservé = 49220 Ko, engagé = 49220 Ko) 
 
 - Compilateur (réservé = 267 Ko, engagé = 267 Ko) 
 (Malloc = 137 Ko # 333) 
 (Arène = 131 Ko # 3) 
 
 - Interne (réservé = 65106 Ko, engagé = 65106 Ko) 
 (Malloc = 65074 Ko # 29652) 
 (mmap: réservé = 32 Ko, engagé = 32 Ko) 
 
 - Symbole (réservé = 13622 Ko, engagé = 13622 Ko) 
 (malloc = 12016 Ko # 128199) 
 (arène = 1606 Ko # 1) 
 
 - Suivi de la mémoire native (réservé = 3361 Ko, engagé = 3361 Ko) 
 (malloc = 287KB # 3994) 
 (suivi des frais généraux = 3075KB) 
 
 - Arena Chunk (réservé = 220KB, engagé = 220KB) 
 (malloc = 220KB) 

Cela montre 2,7 Go de mémoire validée, dont 1,3 Go de tas alloué et près de 1,2 Go de piles de threads allouées (en utilisant de nombreux threads).

Cependant, lors de l'exécution de ps ax -o pid,rss | grep <mypid> ou top il n'affiche que 1,6 Go de RES/rss mémoire résidente. La vérification du swap indique qu'aucun n'est utilisé:

 free -m 
 total des tampons partagés libres utilisés mis en cache 
 Mem: 129180 99348 29831 0 2689 73024 
 -/+ buffers/cache: 23633 105546 
 Échange: 15624 0 15624 

Pourquoi la JVM indique-t-elle que 2,7 Go de mémoire sont engagés alors que 1,6 Go seulement résident? Où est passé le reste?

31
Dave L.

Je commence à soupçonner que la mémoire de pile (contrairement au tas JVM) semble être pré-engagée sans devenir résidente et au fil du temps ne devient résidente que jusqu'à la limite supérieure de l'utilisation réelle de la pile.

Oui, au moins sur linux mmap est paresseux sauf indication contraire. Les pages ne sont sauvegardées par la mémoire physique qu'une fois écrites (les lectures ne sont pas suffisantes en raison de optimisation zéro page )

La mémoire de tas GC est effectivement touchée par le collecteur de copie ou par la pré-mise à zéro (-XX:+AlwaysPreTouch), il sera donc toujours résident. Les piles de fils ne sont pas affectées par cela.

Pour plus de confirmation, vous pouvez utiliser pmap -x <Java pid> et croiser le RSS de diverses plages d'adresses avec la sortie de la carte de mémoire virtuelle de NMT.


La mémoire réservée a été mappée avec PROT_NONE. Ce qui signifie que les plages d'espace d'adressage virtuel ont des entrées dans les structures vma du noyau et ne seront donc pas utilisées par d'autres appels mmap/malloc. Mais ils entraîneront toujours des défauts de page transmis au processus en tant que SIGSEGV, c'est-à-dire que leur accès est une erreur.

Il est important de disposer de plages d'adresses contiguës pour une utilisation future, ce qui simplifie à son tour l'arithmétique des pointeurs.

La mémoire de stockage validée mais non sauvegardée a été mappée avec - par exemple - PROT_READ | PROT_WRITE mais y accéder provoque toujours un défaut de page. Mais cette erreur de page est gérée silencieusement par le noyau en la sauvegardant avec de la mémoire réelle et en retournant à l'exécution comme si de rien n'était.
C'est à dire. c'est un détail/optimisation d'implémentation qui ne sera pas remarqué par le processus lui-même.


Pour donner une ventilation des concepts:

Tas utilisé : la quantité de mémoire occupée par les objets vivants selon le dernier GC

Validé : plages d'adresses qui ont été mappées avec autre chose que PROT_NONE. Ils peuvent ou non être soutenus par un physique ou un échange en raison d'une allocation et d'une pagination paresseuses.

Réservé : La plage d'adresses totale qui a été pré-mappée via mmap pour un pool de mémoire particulier.
La différence réservée - engagée consiste en PROT_NONE mappages, qui ne sont pas garantis par la mémoire physique

Résident : Pages actuellement en mémoire physique. Cela signifie du code, des piles, une partie des pools de mémoire validés mais également des portions de fichiers mmapés auxquels on a récemment accédé et des allocations hors du contrôle de la JVM.

Virtuel : somme de tous les mappages d'adresses virtuelles. Couvre les pools de mémoire réservés et validés, mais également les fichiers mappés ou la mémoire partagée. Ce nombre est rarement informatif car la JVM peut réserver à l'avance de très grandes plages d'adresses ou mmap de gros fichiers.

31
the8472