web-dev-qa-db-fra.com

Java utilisant beaucoup plus de mémoire que la taille de segment de mémoire (ou dimensionner correctement la limite de mémoire Docker)

Pour mon application, la mémoire utilisée par le processus Java est beaucoup plus que la taille du segment de mémoire.

Le système sur lequel les conteneurs sont exécutés commence à avoir un problème de mémoire car le conteneur utilise beaucoup plus de mémoire que la taille du segment de mémoire.

La taille de segment de mémoire est définie sur 128 Mo (-Xmx128m -Xms128m), tandis que le conteneur utilise jusqu'à 1 Go de mémoire. Dans des conditions normales, il faut 500 Mo. Si le conteneur de menu fixe a une limite inférieure à (par exemple mem_limit=mem_limit=400MB), le processus est tué par le destructeur de mémoire insuffisante du système d'exploitation.

Pouvez-vous expliquer pourquoi le processus Java utilise beaucoup plus de mémoire que le tas? Comment dimensionner correctement la limite de mémoire de Docker? Existe-t-il un moyen de réduire l'encombrement mémoire hors processus du processus Java?


Je rassemble quelques détails sur le problème en utilisant la commande de Suivi de la mémoire native dans JVM .

Du système hôte, je récupère la mémoire utilisée par le conteneur.

$ docker stats --no-stream 9afcb62a26c8
CONTAINER ID        NAME                                                                                        CPU %               MEM USAGE / LIMIT   MEM %               NET I/O             BLOCK I/O           PIDS
9afcb62a26c8        xx-xxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.0acbb46bb6fe3ae1b1c99aff3a6073bb7b7ecf85   0.93%               461MiB / 9.744GiB   4.62%               286MB / 7.92MB      157MB / 2.66GB      57

De l'intérieur du conteneur, je récupère la mémoire utilisée par le processus.

$ ps -p 71 -o pcpu,rss,size,vsize
%CPU   RSS  SIZE    VSZ
11.2 486040 580860 3814600

$ jcmd 71 VM.native_memory
71:

Native Memory Tracking:

Total: reserved=1631932KB, committed=367400KB
-                 Java Heap (reserved=131072KB, committed=131072KB)
                            (mmap: reserved=131072KB, committed=131072KB) 

-                     Class (reserved=1120142KB, committed=79830KB)
                            (classes #15267)
                            (  instance classes #14230, array classes #1037)
                            (malloc=1934KB #32977) 
                            (mmap: reserved=1118208KB, committed=77896KB) 
                            (  Metadata:   )
                            (    reserved=69632KB, committed=68272KB)
                            (    used=66725KB)
                            (    free=1547KB)
                            (    waste=0KB =0.00%)
                            (  Class space:)
                            (    reserved=1048576KB, committed=9624KB)
                            (    used=8939KB)
                            (    free=685KB)
                            (    waste=0KB =0.00%)

-                    Thread (reserved=24786KB, committed=5294KB)
                            (thread #56)
                            (stack: reserved=24500KB, committed=5008KB)
                            (malloc=198KB #293) 
                            (arena=88KB #110)

-                      Code (reserved=250635KB, committed=45907KB)
                            (malloc=2947KB #13459) 
                            (mmap: reserved=247688KB, committed=42960KB) 

-                        GC (reserved=48091KB, committed=48091KB)
                            (malloc=10439KB #18634) 
                            (mmap: reserved=37652KB, committed=37652KB) 

-                  Compiler (reserved=358KB, committed=358KB)
                            (malloc=249KB #1450) 
                            (arena=109KB #5)

-                  Internal (reserved=1165KB, committed=1165KB)
                            (malloc=1125KB #3363) 
                            (mmap: reserved=40KB, committed=40KB) 

-                     Other (reserved=16696KB, committed=16696KB)
                            (malloc=16696KB #35) 

-                    Symbol (reserved=15277KB, committed=15277KB)
                            (malloc=13543KB #180850) 
                            (arena=1734KB #1)

-    Native Memory Tracking (reserved=4436KB, committed=4436KB)
                            (malloc=378KB #5359) 
                            (tracking overhead=4058KB)

-        Shared class space (reserved=17144KB, committed=17144KB)
                            (mmap: reserved=17144KB, committed=17144KB) 

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

-                   Logging (reserved=4KB, committed=4KB)
                            (malloc=4KB #179) 

-                 Arguments (reserved=19KB, committed=19KB)
                            (malloc=19KB #512) 

-                    Module (reserved=258KB, committed=258KB)
                            (malloc=258KB #2356) 

$ cat /proc/71/smaps | grep Rss | cut -d: -f2 | tr -d " " | cut -f1 -dk | sort -n | awk '{ sum += $1 } END { print sum }'
491080

L’application est un serveur Web utilisant Jetty/Jersey/CDI, regroupé dans un espace de 36 Mo.

Les versions suivantes de OS et Java sont utilisées (à l'intérieur du conteneur). L'image Docker est basée sur openjdk:11-jre-slim.

$ Java -version
openjdk version "11" 2018-09-25
OpenJDK Runtime Environment (build 11+28-Debian-1)
OpenJDK 64-Bit Server VM (build 11+28-Debian-1, mixed mode, sharing)
$ uname -a
Linux service1 4.9.125-linuxkit #1 SMP Fri Sep 7 08:20:28 UTC 2018 x86_64 GNU/Linux

https://Gist.github.com/prasanthj/48e7063cac88eb396bc9961fb3149b58

60
Nicolas Henneaux

https://developers.redhat.com/blog/2017/04/04/openjdk-and-containers/ :

Pourquoi est-ce lorsque je spécifie -Xmx = 1g, ma machine virtuelle utilise plus de mémoire que 1 Go de mémoire?

Spécifier -Xmx = 1g indique à la machine virtuelle Java d'allouer un segment de mémoire de 1 Go. Ce n'est pas indiquant à la machine virtuelle Java de limiter l'utilisation totale de sa mémoire à 1 Go. Il y a tables de cartes, caches de code, et toutes sortes d'autres données off-tas structures. Le paramètre que vous utilisez pour spécifier l'utilisation totale de la mémoire est -XX: MaxRAM. Sachez qu'avec -XX: MaxRam = 500m, votre tas sera d'environ 250 Mo.

Java voit la taille de la mémoire de l'hôte et ne connaît aucune limitation de la mémoire du conteneur. Cela ne crée pas de pression mémoire, donc GC n'a pas besoin de libérer la mémoire utilisée. J'espère que XX:MaxRAM vous aidera à réduire l'encombrement de la mémoire. Finalement, vous pouvez modifier la configuration du GC (-XX:MinHeapFreeRatio, -XX:MaxHeapFreeRatio, ...) 


Il existe de nombreux types de métriques de mémoire. Docker semble signaler la taille de la mémoire RSS, qui peut être différente de la mémoire "validée" indiquée par jcmd (les anciennes versions de Docker signalent que le cache RSS + est utilisé en mémoire). Bonne discussion et liens: Différence entre la taille de l'ensemble de résidents (RSS) et la mémoire totale validée (NMT) de Java pour une machine virtuelle Java s'exécutant dans le conteneur Docker

La mémoire (RSS) peut également être consommée par d'autres utilitaires du conteneur - Shell, gestionnaire de processus, ... Nous ne savons pas ce qui se passe dans le conteneur ni comment démarrer les processus dans le conteneur.

10
Jan Garaj

TL; DR

L'utilisation détaillée de la mémoire est fournie par les détails du suivi de la mémoire native (NMT) (principalement les métadonnées de code et le ramasse-miettes). En plus de cela, le compilateur et l'optimiseur Java C1/C2 utilisent la mémoire non indiquée dans le résumé.

L'empreinte mémoire peut être réduite à l'aide des indicateurs JVM (mais il y a des impacts).

Le dimensionnement du conteneur Docker doit être effectué par des tests avec la charge attendue de l'application.


Détail pour chaque composant

L'espace de classe shared peut être désactivé dans un conteneur, car les classes ne seront pas partagées par un autre processus JVM. Le drapeau suivant peut être utilisé. Cela supprimera l'espace de classe partagé (17 Mo).

-Xshare:off

Le garbage collector serial a une empreinte mémoire minimale au prix d'un temps de pause plus long lors du traitement du ramassage des ordures (voir Comparaison d'Aleksey Shipilëv entre GC dans une image ). Il peut être activé avec le drapeau suivant. Il peut économiser jusqu’à l’espace utilisé par le GC (48 Mo).

-XX:+UseSerialGC

Le compilateur C2 peut être désactivé avec l'indicateur suivant afin de réduire les données de profilage utilisées pour décider d'optimiser ou non une méthode.

-XX:+TieredCompilation -XX:TieredStopAtLevel=1

L'espace de code est réduit de 20 Mo. De plus, la mémoire en dehors de la machine virtuelle Java est réduite de 80 Mo (différence entre l’espace NMT et l’espace RSS). Le compilateur d'optimisation C2 a besoin de 100 Mo.

Les compilateurs C1 et C2 peuvent être désactivés avec l'indicateur suivant.

-Xint

La mémoire en dehors de la machine virtuelle est maintenant inférieure à l'espace total engagé. L'espace de code est réduit de 43 Mo. Attention, cela a un impact majeur sur les performances de l'application. La désactivation des compilateurs C1 et C2 réduit la mémoire utilisée de 170 Mo.

L'utilisation de Graal VM du compilateur (remplacement de C2) entraîne un encombrement mémoire un peu plus réduit. Il augmente de 20 Mo l'espace mémoire du code et diminue de 60 Mo hors de la mémoire de la machine virtuelle Java.

L'article Gestion de la mémoire Java pour JVM fournit des informations pertinentes sur les différents espaces mémoire . Oracle fournit des informations détaillées dans Documentation sur le suivi de la mémoire native . Plus de détails sur le niveau de compilation dans stratégie de compilation avancée et dans disable C2 réduisent la taille du cache de code d'un facteur 5 . Quelques détails sur Pourquoi une machine virtuelle Java signale-t-elle plus de mémoire dédiée que la taille de l'ensemble résident du processus Linux? lorsque les deux compilateurs sont désactivés.

4
Nicolas Henneaux

Toutes les réponses ci-dessus vous expliquent pourquoi JVM utilise autant de mémoire, mais peut-être avez-vous le plus besoin d'une solution, ces articles vous aideront:
- https://blogs.Oracle.com/Java-platform-group/Java-se-support-for-docker-cpu-and-memory-limits
- https://royvanrijn.com/blog/2018/05/Java-and-docker-memory-limits/

0
Jason Wong