web-dev-qa-db-fra.com

Différence entre la taille de l'ensemble résident (RSS) et Java mémoire totale engagée (NMT) pour une machine virtuelle Java exécutée dans un conteneur Docker

Scénario:

J'ai une machine virtuelle Java exécutée dans un conteneur Docker. J'ai fait une analyse de la mémoire en utilisant deux outils: 1) haut 2) Suivi de la mémoire native Java . Les chiffres semblent confus et j'essaie de trouver ce qui cause les différences.

Question:

Le RSS est signalé comme 1272 Mo pour le processus Java et la mémoire totale Java est signalée comme 790,55 Mo. Comment puis-je expliquer d'où vient le reste de la mémoire 1272 - 790,55 = 481,44 Mo aller?

Pourquoi je veux garder ce problème ouvert même après avoir regardé cette question sur SO:

J'ai vu la réponse et l'explication est logique. Cependant, après avoir obtenu la sortie de Java NMT et pmap -x, je ne suis toujours pas en mesure de cartographier concrètement qui Java les adresses mémoire sont en fait résidentes et physiquement mappées . J'ai besoin d'explications concrètes (avec des étapes détaillées) pour trouver la cause de cette différence entre RSS et Java Mémoire totale engagée .

Sortie supérieure

enter image description here

Java NMT

enter image description here

Statistiques de la mémoire Docker

enter image description here

Graphiques

J'ai un conteneur docker qui fonctionne pendant plus de 48 heures. Maintenant, quand je vois un graphique qui contient:

  1. Mémoire totale accordée au conteneur Docker = 2 Go
  2. Java Max Heap = 1 Go
  3. Total engagé (JVM) = toujours inférieur à 800 Mo
  4. Tas utilisé (JVM) = toujours inférieur à 200 Mo
  5. Non Heap Used (JVM) = toujours inférieur à 100 Mo.
  6. RSS = environ 1,1 Go.

Alors, qu'est-ce qui mange de la mémoire entre 1,1 Go (RSS) et 800 Mo (mémoire totale Java)?

enter image description here

34
sunsin1985

Vous avez des indices dans " Analyse de Java utilisation de la mémoire dans un conteneur Docker " de Mikhail Krestjaninoff :

(Et pour être clair, en mai 2019, trois ans plus tard, la situation s'améliore avec openJDK 8u212 )

[~ # ~] r [~ # ~] esident [~ # ~] s [~ # ~] et [~ # ~] s [~ # ~] ize est la quantité de mémoire physique actuellement allouée et utilisée par un processus (sans échange de pages). Il comprend le code, les données et les bibliothèques partagées (qui sont comptabilisées dans chaque processus qui les utilise)

Pourquoi les informations sur les statistiques de Docker diffèrent-elles des données PS?

La réponse à la première question est très simple - Docker a un bug (ou une fonctionnalité - dépend de votre humeur) : il inclut des caches de fichiers dans les informations d'utilisation totale de la mémoire. Ainsi, nous pouvons simplement éviter cette métrique et utiliser ps info sur RSS.

Eh bien, ok - mais pourquoi RSS est-il supérieur à Xmx?

Théoriquement, dans le cas d'une application Java

RSS = Heap size + MetaSpace + OffHeap size

où OffHeap se compose de piles de threads, de tampons directs, de fichiers mappés (bibliothèques et jars) et de code JVM itse

Depuis JDK 1.8.4 nous avons Native Memory Tracker!

Comme vous pouvez le voir, j'ai déjà ajouté -XX:NativeMemoryTracking=summary propriété à la JVM, nous pouvons donc simplement l'invoquer depuis la ligne de commande:

docker exec my-app jcmd 1 VM.native_memory summary

(C'est ce que le PO a fait)

Ne vous inquiétez pas de la section "Inconnu" - semble que NMT est un outil immature et ne peut pas traiter avec CMS GC (cette section disparaît lorsque vous utilisez un autre GC).

Gardez à l'esprit, que NMT affiche la mémoire "engagée", pas "résidente" (que vous obtenez par la commande ps). En d'autres termes, une page mémoire peut être validée sans être considérée comme résidente (jusqu'à ce qu'elle y accède directement) .

Cela signifie que les résultats NMT pour les zones non-tas (le tas est toujours préinitialisé) peuvent être plus grands que les valeurs RSS .

(c'est là que " Pourquoi une JVM rapporte-t-elle plus de mémoire engagée que la taille de l'ensemble résident du processus Linux? " entre en jeu)

Par conséquent, malgré le fait que nous avons défini la limite de tas jvm à 256 m, notre application consomme 367 Mo. Les "autres" 164M sont principalement utilisés pour stocker les métadonnées de classe, le code compilé, les threads et les données GC.

Les trois premiers points sont souvent des constantes pour une application, donc la seule chose qui augmente avec la taille du tas, ce sont les données GC.
Cette dépendance est linéaire, mais le coefficient "k" (y = kx + b) est beaucoup moins que 1.


Plus généralement, cela semble être suivi par problème 1502 qui signale un problème similaire depuis Docker 1.7

J'exécute une simple application Scala (JVM) qui charge beaucoup de données dans et hors de la mémoire.
J'ai défini la JVM sur un tas 8G (-Xmx8G). J'ai une machine avec une mémoire de 132 Go et elle ne peut pas gérer plus de 7 à 8 conteneurs car ils dépassent largement la limite de 8 Go que j'ai imposée à la JVM.

( docker stat était signalé comme trompeur auparavant , car il inclut apparemment des caches de fichiers dans les informations d'utilisation totale de la mémoire)

docker stat montre que chaque conteneur utilise lui-même beaucoup plus de mémoire que la JVM ne devrait en utiliser. Par exemple:

CONTAINER CPU % MEM USAGE/LIMIT MEM % NET I/O
dave-1 3.55% 10.61 GB/135.3 GB 7.85% 7.132 MB/959.9 MB
perf-1 3.63% 16.51 GB/135.3 GB 12.21% 30.71 MB/5.115 GB

Il semble presque que la JVM demande au système d'exploitation de la mémoire, qui est allouée dans le conteneur, et la JVM libère de la mémoire pendant l'exécution de son GC, mais le conteneur ne libère pas la mémoire. au système d'exploitation principal. Alors ... fuite de mémoire.

33
VonC