305 votes

Utilisation de la mémoire virtuelle de Java sous Linux, trop de mémoire utilisée

J'ai un problème avec une application Java fonctionnant sous Linux.

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

La mémoire virtuelle réservée ne sera pas utilisée de toute façon, d'après ce que j'ai compris, parce qu'une fois que nous atteignons la limite du tas, une 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 celle du tas sont similaires.

Est-il possible de configurer la mémoire virtuelle utilisée par un processus Java sous Linux ?

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

Edit 2 : Utilisation de ulimit -v permet de limiter la quantité de mémoire virtuelle. Si la taille définie est inférieure à 204 Mo, l'application ne fonctionnera 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. Peut-on changer cela ?

Edit 3 : Il y a plusieurs autres applications qui tournent dans le système, qui est embarqué. Et le système a une limite de mémoire virtuelle (d'après les commentaires, détail important).

0 votes

Pourquoi vous préoccupez-vous de l'utilisation de la mémoire virtuelle ? Si vous voulez vraiment être concerné, regardez l'utilisation de la mémoire résidente et lisez les commandes suivantes : free, ps, top.

2 votes

Il y a plusieurs autres applications fonctionnant dans le système, qui est embarqué. Et le système a une limite de mémoire virtuelle.

0 votes

Ahhhhh, le diable est dans les détails

722voto

kdgregory Points 21849

C'est un reproche que l'on fait depuis longtemps à Java, mais il est en grande partie dénué de sens et repose généralement sur l'examen de la mauvaise information. La formulation habituelle est quelque chose comme "Hello World sur Java prend 10 mégaoctets ! Pourquoi a-t-il besoin de cela ?" Eh bien, voici un moyen de faire en sorte que Hello World sur une JVM 64 bits prétende prendre plus de 4 gigaoctets ... au moins selon une forme de mesure.

java -Xms1024m -Xmx4096m com.example.Hello

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

Sous Linux, le top vous donne plusieurs nombres différents pour la mémoire. Voici ce qu'elle indique pour l'exemple Hello World :

  PID USER      PR  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 dans la carte mémoire virtuelle (voir ci-dessous). Il n'a pratiquement aucune signification, sauf quand il n'en a pas (voir ci-dessous).
  • RES est la taille de l'ensemble résident : le nombre de pages qui sont actuellement résidentes en RAM. Dans presque tous les cas, c'est le seul chiffre que vous devez utiliser lorsque vous dites "trop gros". 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 qui est partagée avec d'autres processus. Pour un processus Java, cela se limite généralement aux bibliothèques partagées et aux fichiers JAR mappés en mémoire. Dans cet exemple, je n'avais qu'un seul processus Java en cours d'exécution, donc je soupçonne que les 7k sont le résultat des 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, qu'il soit ou non dans l'espace d'échange. . Le système d'exploitation est très efficace pour conserver les pages actives en mémoire vive, et les seuls remèdes au swapping sont (1) d'acheter plus de mémoire, ou (2) de réduire le nombre de processus, donc il est préférable d'ignorer ce nombre.

La situation du gestionnaire de tâches de Windows est un peu plus compliquée. Sous Windows XP, il y a des colonnes "Utilisation de la mémoire" et "Taille de la mémoire virtuelle", mais la colonne documentation officielle est silencieux sur ce qu'ils signifient. Windows Vista et Windows 7 ajoutent des colonnes supplémentaires, et elles sont en fait documenté . Parmi celles-ci, la mesure "Working Set" est la plus utile ; elle correspond approximativement à la somme de RES et SHR sous Linux.

Comprendre la carte de mémoire virtuelle

La mémoire virtuelle consommée par un processus est le total de tout ce qui se trouve dans la carte mémoire du processus. Cela inclut les données (par exemple, le tas Java), mais aussi 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 fonction pmap pour voir tout ce qui est cartographié dans l'espace des processus (à partir de maintenant, je ne ferai référence qu'à Linux, car c'est ce que j'utilise ; je suis sûr qu'il existe des outils équivalents pour Windows). Voici un extrait de la carte mémoire du programme "Hello World" ; la carte mémoire entière fait plus de 100 lignes, et il n'est pas rare d'avoir une liste de mille lignes.

0000000040000000     36K r-x--  /usr/local/java/jdk-1.6-x64/bin/java
0000000040108000      8K rwx--  /usr/local/java/jdk-1.6-x64/bin/java
0000000040eba000    676K rwx--    \[ anon \]
00000006fae00000  21248K rwx--    \[ anon \]
00000006fc2c0000  62720K rwx--    \[ anon \]
0000000700000000 699072K rwx--    \[ 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
...
00007fa1ed1d3000   1024K rwx--    \[ anon \]
00007fa1ed2d3000      4K -----    \[ anon \]
00007fa1ed2d4000   1024K rwx--    \[ anon \]
00007fa1ed3d4000      4K -----    \[ anon \]
...
00007fa1f20d3000    164K r-x--  /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
00007fa1f20fc000   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 r-x--  /lib/x86\_64-linux-gnu/libc-2.13.so
00007fa1f3634000   2044K -----  /lib/x86\_64-linux-gnu/libc-2.13.so
00007fa1f3833000     16K r-x--  /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 la mémoire virtuelle du segment. Elle est suivie de la taille du segment, des permissions et de la source du segment. Ce dernier élément est soit un fichier, soit "anon", ce qui indique un bloc de mémoire alloué par l'intermédiaire de mmap .

En commençant par le haut, nous avons

  • Le chargeur JVM (c'est-à-dire le programme qui est exécuté lorsque vous tapez java ). C'est très peu ; tout ce qu'il fait est de charger les bibliothèques partagées où le vrai code JVM est stocké.
  • Un tas de blocs anonymes contenant le tas Java et les données internes. Il s'agit d'une JVM Sun, donc le tas est divisé en plusieurs générations, chacune d'entre elles étant son propre bloc de mémoire. Notez que la JVM alloue l'espace mémoire virtuel en se basant sur le code -Xmx ce qui lui permet d'avoir un tas contigu. Le site -Xms est utilisée en interne pour indiquer la part du tas qui est "utilisée" lorsque le programme démarre, et pour déclencher le ramassage des déchets lorsque cette limite est atteinte.
  • Un fichier JAR mappé en mémoire, dans ce cas le fichier qui contient les "classes JDK". Lorsque vous mappez un JAR en mémoire, vous pouvez accéder aux fichiers qu'il contient de manière très efficace (au lieu de le lire depuis le début à chaque fois). La JVM de Sun mappe en mémoire tous les JAR du classpath ; si votre code d'application doit accéder à un JAR, vous pouvez également le mapper en mémoire.
  • Données par thread pour deux threads. Le bloc de 1M est la pile du thread. Je n'avais pas de bonne explication pour le bloc de 4k, mais @ericsoe l'a identifié comme un "bloc de garde" : il n'a pas de permissions de lecture/écriture, et causera donc une faute de segment si on y accède, et la JVM attrape cela et le traduit en un StackOverFlowError . 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 qui contient le code JVM réel. Il en existe plusieurs.
  • La bibliothèque partagée pour la bibliothèque standard C. Ce n'est qu'une des nombreuses choses que la JVM charge et qui ne font 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 les données globales par processus pour la bibliothèque (je ne sais pas ce qu'est le segment sans permissions ; 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 qui utilisent la bibliothèque ; par exemple, libc dispose de 1,5M d'espace mémoire virtuel qui peut être partagé.

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

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

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 vous devrez peut-être faire des compromis, par exemple en réduisant 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 qu'il faudra longtemps avant que la taille de la mémoire virtuelle ne soit une statistique totalement inutile.

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

La taille de l'ensemble résident est la portion de l'espace mémoire virtuel qui se trouve réellement dans la RAM. Si votre RSS devient une partie importante de votre mémoire physique totale, il est peut-être temps de commencer à vous inquiéter. Si votre RSS occupe toute votre mémoire physique et que votre système commence à faire du swapping, il est grand temps de vous inquiéter.

Mais le RSS est également trompeur, surtout 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. Il y a peu d'avantages à le faire, et le potentiel d'un défaut de page coûteux si le processus touche la page à l'avenir. Par conséquent, la statistique RSS peut inclure beaucoup de pages qui ne sont pas utilisées activement.

Ligne de fond

À moins que vous ne fassiez du swapping, ne vous préoccupez pas trop de ce que les diverses statistiques de mémoire vous disent. Avec la réserve qu'un RSS en constante augmentation peut indiquer une sorte de fuite de mémoire.

Avec un programme Java, il est bien plus important de prêter attention à ce qui se passe dans le tas. La quantité totale d'espace consommé est importante, et il existe certaines mesures que vous pouvez prendre pour la réduire. Ce qui est plus important, c'est le temps que vous passez dans la collecte des déchets, et quelles parties du tas sont collectées.

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

9 votes

Vous devez prendre en compte le fait que les portions de mémoire qui sont actuellement échangées ne sont pas prises en compte dans la mesure de RES. Ainsi, vous pouvez avoir une valeur RES faible mais uniquement parce que l'application était inactive et qu'une grande partie du tas a été transférée sur le disque. Java fait un très mauvais travail en matière de swap : À chaque GC complète, la plupart du tas est parcouru et copié, donc si une grande partie de votre tas était en swap, le GC doit tout recharger dans la mémoire principale.

1 votes

Excellente réponse kdgregory ! Je fonctionne dans un environnement embarqué en utilisant un CF qui n'a PAS d'espace d'échange. Donc, en se basant sur votre réponse, toutes mes valeurs VIRT, SWAP et nFLT proviennent de fichiers mappés en mémoire... ce qui a maintenant un sens pour moi. Savez-vous si la valeur SWAP représente des pages qui n'ont pas encore été chargées en mémoire ou des pages qui ont été échangées hors de la mémoire, ou les deux ? Comment pouvons-nous avoir une idée d'un éventuel thrashing (map in continu puis swap out) ?

2 votes

@Jeach - J'ai été surpris qu'un swap soit signalé, j'ai donc démarré mon "Linux de voyage" (une clé USB avec Ubuntu 10.04 et aucun swap). Lorsque j'ai activé la colonne "SWAP" dans le champ top j'ai vu qu'Eclipse avait 509m. Quand je l'ai ensuite regardé avec pmap L'espace virtuel total était de 650 mètres. Je soupçonne donc que le chiffre "SWAP" représente toutes les pages sur le disque, et pas seulement celles qui ne sont pas en mémoire.

11voto

James Schek Points 11070

La quantité de mémoire allouée au processus Java correspond à peu près à ce que j'attendais. J'ai eu des problèmes similaires en exécutant Java sur des systèmes embarqués/à mémoire limitée. Exécution de tout Les applications avec des limites arbitraires de VM ou sur des systèmes qui n'ont pas une quantité adéquate de swap ont tendance à se casser. Cela semble être la nature de nombreuses applications modernes qui ne sont pas conçues pour être utilisées sur des systèmes à ressources limitées.

Vous disposez de quelques options supplémentaires pour essayer de limiter l'empreinte mémoire de votre JVM. Cela pourrait réduire l'empreinte de la mémoire virtuelle :

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

-XX:MaxPermSize=64m Taille de la génération permanente. [5.0 et plus récents : Les VM 64 bits sont mises à l'échelle 30% plus grandes ; 1.4 amd64 : 96m ; 1.3.1 -client : 32m].

De plus, vous devriez également définir votre -Xmx (taille maximale du tas) à une valeur aussi proche que possible de la valeur de pic d'utilisation réelle de la mémoire de votre application. Je crois que le comportement par défaut de la JVM est toujours de double la taille du tas à chaque fois qu'il l'agrandit jusqu'au maximum. Si vous commencez avec un tas de 32M et que votre application atteint 65M, alors le tas finira par augmenter de 32M -> 64M -> 128M.

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

-XX:MinHeapFreeRatio=40 Pourcentage minimum du tas libre après la GC pour éviter une pour éviter l'expansion.

De plus, d'après ce que je me rappelle avoir expérimenté avec ce système il y a quelques années, le nombre de bibliothèques natives chargées avait un impact énorme sur l'empreinte minimale. Le chargement de java.net.Socket ajoutait plus de 15M si je me souviens bien (et je ne me souviens probablement pas).

8voto

La JVM de Sun a besoin de beaucoup de mémoire pour HotSpot et elle mappe les bibliothèques d'exécution dans la mémoire partagée.

Si la mémoire est un problème, envisagez d'utiliser une autre JVM adaptée à l'intégration. IBM a j9, et il y a l'Open Source "jamvm" qui utilise les bibliothèques classpath de GNU. Sun propose également la JVM Squeak qui fonctionne sur le SunSPOTS, il existe donc des alternatives.

0 votes

Est-il possible de désactiver le hot spot ?

0 votes

Peut-être. Vérifiez les options de la ligne de commande pour la JVM que vous utilisez.

4voto

VonC Points 414372

Juste une idée, mais vous pouvez vérifier l'influence de a ulimit -v opción .

Ce n'est pas une solution réelle puisque cela limiterait l'espace d'adressage disponible pour les tous mais cela vous permettrait de vérifier le comportement de votre application avec une mémoire virtuelle limitée.

0 votes

C'est exactement la nature de mon problème. Mon Heap est fixé à 64M, mais linux réserve 204MB. Si je fixe l'ulimit en dessous de 204, l'application ne fonctionne pas du tout.

0 votes

Intéressant : la définition de l'ulimit peut avoir un effet secondaire involontaire pour d'autres processus, expliquant pourquoi l'application ne peut pas fonctionner.

0 votes

Le problème semble être que Java exige de réserver cette plus grande quantité de mémoire virtuelle même s'il ne l'utilise pas. Sous Windows, la mémoire virtuelle utilisée et le paramètre Xmx sont plutôt proches.

0voto

Paul Tomblin Points 83687

La version java 1.4 de Sun dispose des 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érieure à 1 Mo. Ajoutez la lettre k ou K pour indiquer des kilobytes, ou m ou M pour indiquer des mégaoctets. La valeur par valeur par défaut est de 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érieure à 2 Mo. Ajoutez la lettre k ou K pour indiquer des kilobytes, ou m ou M pour indiquer des mégaoctets. La valeur par valeur par défaut est de 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 d'autres. Voir http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp

1 votes

Le problème que j'ai ne concerne pas la taille du tas, mais la quantité de mémoire virtuelle attribuée par Linux.

0 votes

Lisez l'explication de kdgregory. La réduction de la taille du tas, de la "nouvelle taille" et des autres paramètres configurables permettra de réduire la quantité de mémoire REELLE que le jvm utilise.

0 votes

Il peut avoir un problème légitime. Certaines applications (comme celle que j'ai écrite) mmappent un fichier de 1 Go et certains systèmes ne disposent que de 2 Go de mémoire virtuelle, dont une partie est remplie par des bibliothèques partagées. Et si c'est le problème, il doit absolument désactiver la randomisation DSO. Il y a une option dans /proc.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X