Qu'est-ce qui consomme de la mémoire dans le processus java?

20

nous essayons d'étudier l'utilisation de la mémoire du processus java sous une charge modérée.

  PID   USER    PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
  12663 test    20   0 8378m 6.0g 4492 S   43  8.4 162:29.95 java

Comme vous pouvez le voir, nous avons une mémoire résidente à 6 Go. Maintenant, la partie intéressante est la suivante: le processus est exécuté avec ces paramètres:

  • -Xmx2048m
  • -Xms2048m
  • -XX: NewSize = 512m
  • -XX: MaxDirectMemorySize = 256 m
  • ... quelques autres pour GC et tout ça

En regardant ces paramètres et l'utilisation réelle de la mémoire, nous sommes trébuchés pour voir la différence entre ce que nous attendons de ce processus et ce qu'il utilise réellement.

Habituellement, nos problèmes de mémoire sont résolus en analysant le vidage de tas, mais dans ce cas, notre mémoire est utilisée quelque part en dehors du tas.

Questions: quelles seraient les étapes pour essayer de trouver la raison d'une telle utilisation de la mémoire? Quels outils pourraient nous aider à identifier ce qui utilise la mémoire dans ce processus?

EDIT 0

Il ne semble pas que ce soit un problème lié au tas car nous avons encore beaucoup d'espace là-bas:

jmap -heap 12663

résulte en (édité pour économiser de l'espace)

Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize      = 2147483648 (2048.0MB)
NewSize          = 536870912 (512.0MB)
MaxNewSize       = 536870912 (512.0MB)
OldSize          = 1610612736 (1536.0MB)
NewRatio         = 7
SurvivorRatio    = 8
PermSize         = 21757952 (20.75MB)
MaxPermSize      = 85983232 (82.0MB)

New Generation: 45.7% used
Eden Space: 46.3% used
From Space: 41.4% used
To Space: 0.0% used
concurrent mark-sweep generation: 63.7% used
Perm Generation: 82.5% used

EDIT 1

en utilisant le pmap, nous pouvons voir qu'il existe un certain nombre d'allocations de 64 Mo:

pmap -x 12663 | grep rwx | sort -n -k3 | less

résulte en:

... a lot more of these 64Mb chunks
00007f32b8000000       0   65508   65508 rwx--    [ anon ] <- what are these?
00007f32ac000000       0   65512   65512 rwx--    [ anon ]
00007f3268000000       0   65516   65516 rwx--    [ anon ]
00007f3324000000       0   65516   65516 rwx--    [ anon ]
00007f32c0000000       0   65520   65520 rwx--    [ anon ]
00007f3314000000       0   65528   65528 rwx--    [ anon ] 
00000000401cf000       0  241904  240980 rwx--    [ anon ] <- Direct memory ?
000000077ae00000       0 2139688 2139048 rwx--    [ anon ] <- Heap ?

Alors, comment savoir quels sont ces morceaux de 64 Mo? Qu'est-ce qui les utilise? De quel type de données s'agit-il?

Merci

Konstantin S.
la source
2
J'ai exactement le même problème ... voici ma question. stackoverflow.com/questions/18734389/… Avez-vous une solution à ce sujet?
DeepNightTwo

Réponses:

21

Le problème peut être lié à ce problème de glibc .

Fondamentalement, lorsque vous avez plusieurs threads allouant de la mémoire, la glibc augmentera le nombre d'arènes disponibles pour effectuer l'allocation afin d'éviter les conflits de verrous. Une arène mesure 64 Mo de large. La limite supérieure est de créer 8 fois le nombre d'arènes de noyaux. Les arènes seront créées à la demande lorsqu'un thread accédera à une arène déjà verrouillée afin qu'elle grandisse avec le temps.

En Java où vous saupoudrez de threads, cela peut rapidement conduire à la création de nombreuses arènes. Et il y a des allocations réparties dans toutes ces arènes. Initialement, chaque arène 64 Mo est simplement mappée de mémoire non validée, mais au fur et à mesure que vous effectuez des allocations, vous commencez à utiliser la mémoire réelle pour elles.

Votre pmap a probablement des listes similaires à celle-ci ci-dessous. Remarquez comment 324K + 65212K = 65536K, 560K + 64976K == 65536K, 620K + 64916K == 65536K. Autrement dit, ils résument jusqu'à 64 Mo.

00007f4394000000 324K rw --- [anon]
00007f4394051000 65212K ----- [anon]
00007f4398000000 560K rw --- [anon]
00007f439808c000 64976K ----- [anon]
00007f439c000000 620K rw --- [anon]
00007f439c09b000 64916K ----- [anon]

En ce qui concerne les solutions de contournement : le bogue mentionne certains paramètres d'environnement que vous pouvez définir pour limiter le nombre d'arènes, mais vous avez besoin d'une version glibc suffisamment élevée.

Christian
la source
5
la définition de Xmx et Xms sur la même valeur, ainsi que la définition de la variable d'environnement "export MALLOC_ARENA_MAX = 4" dans le script sh qui démarre notre service Web, ont aidé dans notre cas. Avant cela, nous subissions des redémarrages de service Web en raison de OOM Killer toutes les 2 à 8 heures. La version GLIBC dans Ubuntu 14.04 est 2.19, ce qui est bien, car elle doit être> = 2.16 pour que le paramètre MALLOC_ARENA_MAX fonctionne
Kluyg
Cette réponse et le commentaire ci-dessus m'ont sauvé la vie. Dans mon cas, MALLOC_ARENA_MAX = 1 était nécessaire et efficace.
John Bachir
3

Qu'en est-il de la sonde Lamdba ? Entre autres choses, il peut vous montrer des pannes d'utilisation de la mémoire similaires à la capture d'écran ci-dessous:

Vue d'utilisation de la mémoire de la sonde Lambda

Parfois, cela pmap -x your_java_pidpeut aussi être utile.

Janne Pikkarainen
la source
Merci pour votre réponse. Si je comprends bien, Lambda Probe est pour Apache Tomcat? Ce que nous n'utilisons pas ... En ce qui concerne le pmap, j'ajouterai les informations au premier message
Konstantin S.
2

JProfiler pourrait être quelque chose que vous recherchez, mais ce n'est pas gratuit. Un autre outil bon et gratuit pour étudier l'utilisation de la mémoire du processus java est Java VisualVM disponible en tant qu'outil JDK dans les distributions Oracle / Sun JDK. Je recommanderais personnellement une approche plus holistique du problème (c'est-à-dire pour surveiller les disques JDK + OS +, etc.) - l'utilisation d'un système de surveillance réseau - Nagios, Verax NMS ou OpenNMS.

xantross
la source
2
Sa fuite est hors du tas, donc JProfiler n'aidera pas vraiment ici
Asaf Mesika
2

Le problème est en dehors du tas, donc les meilleurs candidats sont:

JNI leak  
Allocation of direct memory buffer

En raison du fait que vous avez limité la taille du tampon direct, le meilleur candidat à mon avis est la fuite JNI.

Venceslas
la source
1

Il existe un outil pratique pour afficher l'allocation de la mémoire de tas incluse dans le JDK appelée jmap, en plus de cela, vous avez également une pile, etc. (Xss). Exécutez ces deux commandes jmap pour obtenir plus d'informations sur l'utilisation de la mémoire:

jmap -heap <PID>
jmap -permstat <PID>

Pour obtenir encore plus d'informations, vous pouvez vous connecter au processus avec jconsole (également inclus dans le JDK). Jconsole nécessite cependant que JMX soit configuré dans l'application.

HampusLi
la source
Merci pour votre réponse, mais le problème semble être quelque part en dehors du tas. Je mettrai à jour le message supérieur pour refléter certaines informations de jmap
Konstantin S.
Combien de threads le processus a-t-il? La taille de la pile est de 2 Mo par défaut sur la plupart des plates-formes, alors multipliez cela par le nombre de threads. Je serais surpris si cela représente toute la mémoire "manquante" mais peut-être une partie de celle-ci.
HampusLi
Environ 300 threads, pris des informations de pmap, nous avons une taille de pile de 1 Mo. Et à partir de la même sortie pmap, il ne semble pas que ces piles utilisent plus de 100 Ko
Konstantin S.
0

Utilisez JVisualVM. Il a différentes vues différentes qui vous diront combien de mémoire de tas est utilisée, PermGen et ainsi de suite.

Quant à répondre à votre question. Java gère la mémoire très différemment de ce à quoi vous pourriez vous attendre.

Lorsque vous définissez les paramètres -Xms et -Xmx, vous dites à la JVM combien de mémoire elle doit allouer dans le tas pour commencer et aussi combien elle doit allouer au maximum.

Si vous avez une application java qui a utilisé un total de 1 m de mémoire mais qui est passée en -Xms256m -Xmx2g, la JVM s'initialise elle-même avec 256 m de mémoire utilisée. Il n'en utilisera pas moins. Peu importe que votre application utilise seulement 1 m de mémoire.

Deuxièmement. Dans le cas ci-dessus, si votre application utilise à un moment donné plus de 256 m de mémoire, la machine virtuelle Java allouera autant de mémoire que nécessaire pour traiter la demande. Toutefois, il ne ramènera pas la taille du segment de mémoire à la valeur minimale. Du moins, pas dans la plupart des circonstances.

Dans votre cas, étant donné que vous définissez la mémoire minimale et maximale à 2 g, la JVM allouera 2 g au début et la maintiendra.

La gestion de la mémoire Java est assez complexe et l'optimisation de l'utilisation de la mémoire peut être une tâche en soi. Il existe cependant de nombreuses ressources susceptibles de vous aider.

drone.ah
la source