web-dev-qa-db-fra.com

Utilisation de la mémoire virtuelle sous Java sous Linux, utilisation excessive de la mémoire

J'ai un problème avec une application Java s'exécutant sous Linux.

Lorsque je lance l'application, en utilisant la taille de segment maximale par défaut (64 Mo), l'utilisation de l'application tops indique que 240 Mo de mémoire virtuelle sont alloués à l'application. Cela crée des problèmes avec certains autres logiciels sur l'ordinateur, qui sont relativement limités en ressources.

La mémoire virtuelle réservée ne sera de toute façon pas utilisée, pour autant que je sache, car une fois que nous avons atteint la limite de tas, un OutOfMemoryError est lancé. J'ai exécuté la même application sous Windows et je constate que la taille de la mémoire virtuelle et la taille du segment de mémoire sont similaires.

Puis-je quand même configurer la mémoire virtuelle utilisée pour un processus Java sous Linux?

Edit 1: Le problème n'est pas le tas. Le problème est que si je configure un tas de 128 Mo, par exemple, Linux alloue toujours 210 Mo de mémoire virtuelle, ce qui n’est jamais nécessaire. **

Edit 2: Utiliser ulimit -v permet de limiter la quantité de mémoire virtuelle. Si la taille définie est inférieure à 204 Mo, l'application ne s'exécutera pas, même si elle n'a pas besoin de 204 Mo, mais seulement de 64 Mo. Je veux donc comprendre pourquoi Java nécessite autant de mémoire virtuelle. Cela peut-il être changé?

Edit: Plusieurs autres applications en cours d'exécution sont intégrées au système. Et le système a une limite de mémoire virtuelle (à partir de commentaires, détail important).

241
Mario Ortegón

Il s’agit d’une plainte de longue date avec Java, mais elle est en grande partie dépourvue de sens et repose généralement sur la recherche d’informations erronées. Le libellé habituel est quelque chose comme "Hello World on Java prend 10 mégaoctets! Pourquoi en a-t-il besoin?" Voici un moyen de faire en sorte que Hello World sur une machine JVM 64 bits prétende prendre 4 gigaoctets, au moins par une forme de mesure.

 Java -Xms1024m -Xmx4096m avec.exemple.Bonjour 

Différentes façons de mesurer la mémoire

Sous Linux, la commande top vous donne plusieurs numéros différents pour la mémoire. Voici ce qu'il dit à propos de l'exemple Hello World:

 UTILISATEUR PID NI NI VIRT RES SHR S% CPU% MEM TIME + COMMAND 
 2120 kgregory 20 0 4373m 15m 7152 S 0 0.2 0: 00.10 Java 
  • VIRT est l'espace mémoire virtuel: la somme de tout ce qui se trouve sur la carte mémoire virtuelle (voir ci-dessous). Cela n'a pas beaucoup de sens, sauf quand ce n'est pas le cas (voir ci-dessous).
  • RES est la taille de l'ensemble de résidents: le nombre de pages résidant actuellement dans la RAM. Dans presque tous les cas, c’est le seul chiffre que vous devriez utiliser lorsque vous dites "trop grand". Mais ce n'est toujours pas un très bon chiffre, surtout quand on parle de Java.
  • SHR est la quantité de mémoire résidente partagée avec d'autres processus. Pour un processus Java, il est généralement limité aux bibliothèques partagées et aux fichiers JAR mappés en mémoire. Dans cet exemple, un seul processus Java était en cours d'exécution. Je soupçonne donc que le fichier 7k est le résultat de bibliothèques utilisées par le système d'exploitation.
  • SWAP n'est pas activé par défaut et n'est pas affiché ici. Il indique la quantité de mémoire virtuelle qui réside actuellement sur le disque , que celle-ci se trouve ou non dans l'espace de permutation . Le système d'exploitation tient très bien à garder les pages actives dans la RAM, et les seuls remèdes à l'échange sont (1) d'acheter plus de mémoire ou (2) de réduire le nombre de processus, il est donc préférable d'ignorer ce nombre.

La situation du gestionnaire de tâches Windows est un peu plus compliquée. Sous Windows XP, il existe des colonnes "Utilisation de la mémoire" et "Taille de la mémoire virtuelle", mais le documentation officielle ne dit rien. Windows Vista et Windows 7 ajoutent plus de colonnes et elles sont en fait documentées . Parmi ceux-ci, la mesure "Working Set" est la plus utile; cela correspond à peu près à la somme de RES et SHR sous Linux.

Comprendre la carte de mémoire virtuelle

La mémoire virtuelle consommée par un processus correspond au total de tout ce qui se trouve dans la mappe de mémoire de processus. Cela inclut les données (par exemple, le segment Java, mais également toutes les bibliothèques partagées et les fichiers mappés en mémoire utilisés par le programme. Sous Linux, vous pouvez utiliser la commande pmap pour voir tous les éléments mappés dans l'espace processus (à partir de maintenant, je ne parlerai que de Linux, car c'est ce que j'utilise; je Je suis sûr qu’il existe des outils équivalents pour Windows). Voici un extrait de la carte mémoire du programme "Hello World"; toute la carte mémoire compte plus de 100 lignes et il n’est pas inhabituel d’avoir une liste de milliers de lignes.

 0000000040000000 36K rx-- /usr/local/Java/jdk-1.6-x64/bin/Java[.____.HER0000000040108000 8K rwx-- /usr/local/Java/jdk-1.6-x64/bin /Java W. - [anon] 
 000000072aab0000 2097152K rwx-- [anon] 
 00000007aaab0000 349504K rwx-- [anon] 
 00000007c0000000 1048576K rwx-- [anon] 
. . 
 00007fa1ed00d000 1652K r-xs- /usr/local/Java/jdk-1.6-x64/jre/lib/rt.jar
...[.____0000317fa1ed1d3000 1024K rwx-- [anon] 
 00007fa1ed2d3000 4K ----- [anon] 
 00007fa1ed2d4000 1024K rwx-- [anon] 
 00007fa1ed3d4000 4K ----- [anon] 
 ... ... 
 00007fa1f20d3000 164K rx-- /usr/local/Java/jdk-1.6-x64/jre/lib/AMD64/libjava.so[.____.000000007fa1f20fc000 1020K -----/usr /local/Java/jdk-1.6-x64/jre/lib/AMD64/libjava .so 
 00007fa1f21fb000 28K rwx-- /usr/local/Java/jdk-1.6-x64/jre/lib/AMD64/libjava.so
...
00007fa1f34aa000 1576K rx - /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3634000 2044K ----- /lib/x86_linux-gnu/libc-2.13.so
00007fa1f3833000 16K rx - /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3837000 4K rwx-- /lib/x86_64-linux-gnu/libc-2.13.so 
 ... 

Une explication rapide du format: chaque ligne commence par l'adresse de mémoire virtuelle du segment. Viennent ensuite la taille du segment, les autorisations et la source du segment. Ce dernier élément est soit un fichier, soit "anon", qui indique un bloc de mémoire alloué via mmap .

En partant du haut, nous avons

  • Le chargeur JVM (c'est-à-dire le programme qui s'exécute lorsque vous tapez Java). C'est très petit. tout ce qu'il fait est de charger dans les bibliothèques partagées où le vrai code JVM est stocké.
  • Un tas de blocs anon contenant le Java tas et les données internes. Il s'agit d'une machine virtuelle Sun. Le tas est divisé en plusieurs générations, chacune constituant son propre bloc de mémoire. Notez que la machine virtuelle alloue de la mémoire virtuelle en fonction de la valeur -Xmx. cela lui permet d'avoir un tas contigu. La valeur -Xms est utilisée en interne pour indiquer la quantité de segment de mémoire "en cours d'utilisation" au démarrage du programme et pour déclencher le garbage collection à l'approche de cette limite.
  • Un fichier JAR mappé en mémoire, dans ce cas, le fichier contenant les "classes JDK". Lorsque vous mappez en mémoire un fichier JAR, vous pouvez accéder aux fichiers qu'il contient très efficacement (par opposition à sa lecture depuis le début à chaque fois). La machine virtuelle Java Sun mappera en mémoire tous les fichiers JAR du chemin de classe. si le code de votre application doit accéder à un fichier JAR, vous pouvez également le mapper en mémoire.
  • Données par thread pour deux threads. Le bloc 1M est une pile de threads; Je ne sais pas ce qui se passe dans le bloc 4K. Pour une application réelle, vous verrez des dizaines, voire des centaines de ces entrées répétées dans la carte mémoire.
  • Une des bibliothèques partagées contenant le code de la machine virtuelle Java. Il y en a plusieurs.
  • La bibliothèque partagée pour la bibliothèque standard C. C’est l’une des nombreuses choses que la machine virtuelle Java charge qui ne fait pas strictement partie de Java.

Les bibliothèques partagées sont particulièrement intéressantes: chaque bibliothèque partagée a au moins deux segments: un segment en lecture seule contenant le code de la bibliothèque et un segment en lecture-écriture qui contient des données globales par processus pour la bibliothèque (je ne sais pas ce que le segment sans autorisation est, je ne l'ai vu que sur x64 Linux). La partie en lecture seule de la bibliothèque peut être partagée entre tous les processus utilisant cette bibliothèque. Par exemple, libc dispose de 1,5 M d’espace mémoire virtuel pouvant être partagé.

Quand la taille de la mémoire virtuelle est-elle importante?

La carte de mémoire virtuelle contient beaucoup de choses. Certaines sont en lecture seule, d'autres sont partagées et d'autres sont allouées mais jamais touchées (par exemple, la quasi-totalité des 4 Go de tas dans cet exemple). Mais le système d'exploitation est suffisamment intelligent pour ne charger que ce dont il a besoin, de sorte que la taille de la mémoire virtuelle est en grande partie hors de propos.

La taille de la mémoire virtuelle est importante si vous utilisez un système d'exploitation 32 bits, où vous ne pouvez allouer que 2 Go (ou, dans certains cas, 3 Go) d'espace d'adressage de processus. Dans ce cas, vous avez affaire à une ressource rare et devez faire des compromis, par exemple réduire la taille de votre tas afin de mapper en mémoire un fichier volumineux ou de créer de nombreux threads.

Mais, étant donné que les machines 64 bits sont omniprésentes, je ne pense pas que la taille de la mémoire virtuelle deviendra une statistique totalement non pertinente.

Quand la taille de l'ensemble de résidents est-elle importante?

La taille du groupe de résidents est la partie de la mémoire virtuelle qui est réellement dans la RAM. Si votre RSS devient une partie importante de votre mémoire physique totale, le moment est peut-être venu de vous inquiéter. Si votre RSS utilise de plus en plus de mémoire physique et que votre système commence à permuter, il est grand temps de vous inquiéter.

Mais RSS est également trompeur, en particulier sur une machine peu chargée. Le système d'exploitation ne déploie pas beaucoup d'efforts pour récupérer les pages utilisées par un processus. Cela présente peu d'avantages, et la possibilité d'une erreur de page coûteuse si le processus touche la page à l'avenir. Par conséquent, la statistique RSS peut inclure un grand nombre de pages inutilisées.

Ligne de fond

Sauf si vous échangez, ne vous inquiétez pas trop de ce que vous disent les différentes statistiques sur la mémoire. Avec l'avertissement qu'un flux RSS toujours croissant peut indiquer une sorte de fuite de mémoire.

Avec un programme Java, il est bien plus important de faire attention à ce qui se passe dans le tas. La quantité totale d'espace utilisé est importante et vous pouvez prendre certaines mesures pour la réduire. Le plus important est le temps que vous passez dans la collecte des ordures et les parties du tas qui sont collectées.

L'accès au disque (c'est-à-dire à une base de données) est coûteux et la mémoire peu coûteuse. Si vous pouvez échanger l'un contre l'autre, faites-le.

591
kdgregory

Il existe un problème connu concernant Java et la glibc> = 2.10 (y compris Ubuntu> = 10.04, RHEL> = 6).

Le remède est de régler cette env. variable:

export MALLOC_ARENA_MAX=4

Si vous utilisez Tomcat, vous pouvez ajouter ceci au fichier Tomcat_HOME/bin/setenv.sh.

Pour Docker, ajoutez ceci à Dockerfile

ENV MALLOC_ARENA_MAX=4

Il y a un article sur la configuration IBM MALLOC_ARENA_MAX https: //www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage lang = fr

Cet article de blog dit

la mémoire résidente est connue pour se faufiler de manière similaire à une fuite de mémoire ou à une fragmentation de la mémoire.

Il existe également un bogue JDK ouvert JDK-8193521 "La glibc gaspille de la mémoire avec la configuration par défaut"

recherchez MALLOC_ARENA_MAX sur Google ou SO pour plus de références.

Vous voudrez peut-être également ajuster d'autres options malloc à optimiser pour une faible fragmentation de la mémoire allouée:

# tune glibc memory allocation, optimize for low fragmentation
# limit the number of arenas
export MALLOC_ARENA_MAX=2
# disable dynamic mmap threshold, see M_MMAP_THRESHOLD in "man mallopt"
export MALLOC_MMAP_THRESHOLD_=131072
export MALLOC_TRIM_THRESHOLD_=131072
export MALLOC_TOP_PAD_=131072
export MALLOC_MMAP_MAX_=65536
36
Lari Hotari

La quantité de mémoire allouée pour le processus Java correspond à peu près à ce que j'attendais. J'ai eu des problèmes similaires avec Java sur des systèmes avec une mémoire intégrée ou limitée. L'exécution ( de n'importe quelle application avec des limites arbitraires VM ou sur des systèmes ne disposant pas de la quantité suffisante de swap a tendance à se rompre. Cela semble être la nature de nombreuses applications modernes qui ne sont pas conçues pour être utilisées sur des systèmes aux ressources limitées.

Vous avez quelques options supplémentaires que vous pouvez essayer pour limiter l’empreinte mémoire de votre JVM. Cela pourrait réduire l'encombrement de la mémoire virtuelle:

-XX: ReservedCodeCacheSize = 32m Taille du cache de code réservé (en octets) - taille maximale du cache de code. [Solaris 64 bits, AMD64 et -server x86: 48m; dans 1.5.0_06 et les versions antérieures, Solaris 64 bits et and64: 1024 m.]

-XX: MaxPermSize = 64m Taille de la génération permanente. [5.0 et plus récent: les machines virtuelles 64 bits sont 30% plus grandes; 1,4 AMD64: 96 m; 1.3.1 -client: 32m.]

En outre, vous devez également définir votre -Xmx (taille maximale de segment de mémoire) sur une valeur aussi proche que possible de utilisation de la mémoire de pointe de votre application. Je crois que le comportement par défaut de la machine virtuelle Java consiste toujours à doubler la taille du segment de mémoire à chaque fois qu’il est étendu au maximum. Si vous commencez avec 32 millions de tas et que votre application atteint 65 millions, le tas finira par atteindre une croissance de 32 millions -> 64 millions -> 128 millions.

Vous pouvez également essayer ceci pour rendre la VM moins agressive quant à la croissance du tas:

-XX: MinHeapFreeRatio = 40 Pourcentage minimum de tas libres après GC pour éviter l'expansion.

De plus, d'après ce que je me souviens d'avoir expérimenté avec cela il y a quelques années, le nombre de bibliothèques natives chargées avait un impact énorme sur l'encombrement minimum. Chargement de Java.net.Socket ajouté plus de 15M si je me souviens bien (et probablement pas).

9
James Schek

La machine virtuelle Java Sun nécessite beaucoup de mémoire pour HotSpot et est mappée dans les bibliothèques d'exécution en mémoire partagée.

Si la mémoire pose un problème, envisagez d'utiliser une autre machine virtuelle Java adaptée à l'intégration. IBM a j9 et il existe le "jamvm" Open Source qui utilise les bibliothèques GNU. De plus, Sun a la machine virtuelle Squeak en cours d’exécution sur le SunSPOTS, il existe donc des alternatives.

Un moyen de réduire le volume d'un système avec des ressources limitées peut être de jouer avec la variable -XX: MaxHeapFreeRatio. Ce paramètre est généralement défini sur 70. Il s'agit du pourcentage maximal du segment de mémoire disponible avant que le CPG ne le réduise. Si vous le définissez à une valeur inférieure, vous verrez, par exemple, dans le profileur jvisualvm, qu'un plus petit tas est utilisé pour votre programme.

EDIT: Pour définir de petites valeurs pour -XX: MaxHeapFreeRatio, vous devez également définir -XX: MinHeapFreeRatio Eg

Java -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=25 HelloWorld

EDIT2: Ajout d'un exemple pour une application réelle qui démarre et effectue la même tâche, une avec des paramètres par défaut et une avec 10 et 25 comme paramètres. Je n'ai pas remarqué de différence de vitesse réelle, bien que Java devrait en théorie utiliser plus de temps pour augmenter le tas dans ce dernier exemple.

Default parameters

À la fin, le segment de mémoire maximal est 905, le segment de mémoire utilisé est 378.

MinHeap 10, MaxHeap 25

À la fin, le tas maximum est 722, le tas utilisé est 378

Cela a effectivement un impact, car notre application est exécutée sur un serveur de bureau distant, et de nombreux utilisateurs peuvent l'exécuter en même temps.

3
runholen

Juste une pensée, mais vous pouvez vérifier l’influence de ne option ulimit -v .

Ce n'est pas une solution réelle, car cela limiterait l'espace d'adressage disponible pour le processus all , mais cela vous permettrait de vérifier le comportement de votre application avec un nombre limité. mémoire virtuelle.

3
VonC

Java 1.4 de Sun utilise les arguments suivants pour contrôler la taille de la mémoire:

-Xmsn Spécifiez la taille initiale, en octets, du pool d'allocation de mémoire. Cette valeur doit être un multiple de 1024 supérieur à 1 Mo. Ajoutez la lettre k ou K pour indiquer les kilo-octets, ou m ou M pour indiquer les mégaoctets. La valeur par défaut est 2 Mo. Exemples:

           -Xms6291456
           -Xms6144k
           -Xms6m

-Xmxn Spécifiez la taille maximale, en octets, du pool d'allocation de mémoire. Cette valeur doit être un multiple de 1024 supérieur à 2 Mo. Ajoutez la lettre k ou K pour indiquer les kilo-octets, ou m ou M pour indiquer les mégaoctets. La valeur par défaut est 64 Mo. Exemples:

           -Xmx83886080
           -Xmx81920k
           -Xmx80m

http://Java.Sun.com/j2se/1.4.2/docs/tooldocs/windows/Java.html

Java 5 et 6 en ont encore plus. Voir http://Java.Sun.com/javase/technologies/hotspot/vmoptions.jsp

1
Paul Tomblin

Non, vous ne pouvez pas configurer la quantité de mémoire requise par la machine virtuelle. Cependant, notez qu'il s'agit de mémoire virtuelle, non résidente, elle reste donc là sans risque si elle n'est pas réellement utilisée.

Alternativement, vous pouvez essayer une autre machine virtuelle Java puis celle de Sun, avec une empreinte mémoire plus petite, mais je ne peux pas vous conseiller ici.

0
Marko