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




xmx xms xmn (7)

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

Lorsque je lance l'application, en utilisant la taille de segment de mémoire maximale par défaut (64 Mo), je vois en utilisant l'application tops 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, ce qui est relativement limité en ressources.

La mémoire virtuelle réservée ne sera pas utilisée de toute façon, autant que je OutOfMemoryError , car une fois que nous atteignons la limite de tas, une OutOfMemoryError est levée. J'ai couru la même application sous Windows et je vois que la taille de mémoire virtuelle et la taille de tas sont semblables.

Y at-il de toute façon que je puisse 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 place un tas de 128 Mo, par exemple, Linux alloue 210 Mo de mémoire virtuelle, ce qui n'est pas nécessaire, jamais. **

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 de 64 Mo. Je veux donc comprendre pourquoi Java a besoin de tant de mémoire virtuelle. Cela peut-il être changé?

Édition 3 : Il y a plusieurs autres applications en cours d'exécution dans le système, qui est intégrée. Et le système a une limite de mémoire virtuelle (des commentaires, des détails importants).


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

-Xmsn Spécifie 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 kilo-octets, ou m ou M pour indiquer mégaoctets. La valeur par défaut est 2MB. Exemples:

           -Xms6291456
           -Xms6144k
           -Xms6m

-Xmxn Spécifie 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 kilo-octets, ou m ou M pour indiquer 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 d'autres. Voir http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp


Une façon de réduire le tas 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 et représente le pourcentage maximal du tas qui est libre avant que le GC ne le réduise. En le plaçant à une valeur inférieure, et vous verrez dans le profileur jvisualvm par exemple qu'une plus petite pile de tas est habituellement utilisée 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 fait la même tâche, une avec les 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, même si java en théorie devrait utiliser plus de temps pour augmenter le tas dans le dernier exemple.

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

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

Cela a en fait un certain impact, car notre application fonctionne sur un serveur de bureau à distance, et de nombreux utilisateurs peuvent l'exécuter à la fois.


La quantité de mémoire allouée au processus Java est à peu près équivalente à ce à quoi je m'attendrais. J'ai rencontré des problèmes similaires lors de l'exécution de Java sur des systèmes embarqués / à mémoire limitée. L'exécution de toute application avec des limites de machine virtuelle arbitraires ou sur des systèmes qui n'ont pas suffisamment de permutation a tendance à se rompre. Il 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 avez quelques options supplémentaires que vous pouvez essayer et 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. [Solaris 64 bits, amd64 et -server x86: 48m; dans les versions 1.5.0_06 et antérieures, Solaris 64 bits et and64: 1024m.]

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

En outre, vous devez également définir votre -Xmx (taille de segment maximale) sur une valeur aussi proche que possible de l'utilisation réelle de la mémoire de pointe de votre application. Je crois que le comportement par défaut de la JVM est toujours de doubler la taille du tas chaque fois qu'il l'agrandit au maximum. Si vous commencez avec 32M heap et que votre application atteint un maximum de 65M, alors le tas va finir par augmenter de 32M -> 64M -> 128M.

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

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

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


Non, vous ne pouvez pas configurer la quantité de mémoire requise par VM. Cependant, notez que ceci est de la mémoire virtuelle, pas résident, donc il reste juste là sans dommage s'il n'est pas réellement utilisé.

Alernativement, vous pouvez essayer une autre JVM puis Sun one, avec une empreinte mémoire plus petite, mais je ne peux pas conseiller ici.


Il y a un problème connu avec Java et glibc> = 2.10 (inclut Ubuntu> = 10.04, RHEL> = 6).

Le remède est de mettre cet env. variable: export MALLOC_ARENA_MAX=4 Si vous utilisez Tomcat, vous pouvez l'ajouter au TOMCAT_HOME/bin/setenv.sh .

Il existe un article IBM sur la configuration de 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=en

Ce blog dit

la mémoire résidente a été connue pour se glisser d'une manière similaire à une fuite de mémoire ou à une fragmentation de la mémoire.

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

Vous pouvez également régler d'autres options malloc pour optimiser la 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

Juste une pensée, mais vous pouvez vérifier l'influence d' une option ulimit -v .

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


Cela a été une plainte de longue date avec Java, mais elle est en grande partie vide de sens, et généralement basée sur la mauvaise information. Le phrasé habituel est quelque chose comme "Hello World on Java prend 10 mégaoctets, pourquoi a-t-il besoin de ça?" Eh bien, voici une façon de faire de Hello World sur une revendication JVM 64 bits pour prendre plus de 4 gigaoctets ... au moins par une forme de mesure.

java -Xms1024m -Xmx4096m com.example.Hello

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

Sous Linux, la commande du top vous donne plusieurs nombres différents pour la mémoire. Voici ce qu'il en est de 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 de mémoire virtuelle: la somme de tout dans la carte de mémoire virtuelle (voir ci-dessous). C'est en grande partie vide de sens, sauf quand ce n'est pas le cas (voir ci-dessous).
  • RES est la taille de l'ensemble résidant: le nombre de pages qui résident actuellement dans la RAM. Dans presque tous les cas, c'est le seul chiffre que vous devriez utiliser pour dire "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, cela est généralement limité 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 pense que le 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'apparaît pas ici. Il indique la quantité de mémoire virtuelle résidant actuellement sur le disque, qu'il se trouve ou non dans l'espace d'échange . Le système d'exploitation est très bon pour garder les pages actives en RAM, et les seuls traitements pour l'échange sont (1) acheter plus de mémoire, ou (2) réduire le nombre de processus, il est donc préférable d'ignorer ce nombre.

La situation pour Windows Task Manager 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 la documentation officielle ne dit rien de ce qu'elles signifient. Windows Vista et Windows 7 ajoutent plus de colonnes et sont en fait documented . Parmi ceux-ci, la mesure «Working Set» est la plus utile; il correspond approximativement à la somme de RES et SHR sur Linux.

Comprendre la carte mémoire virtuelle

La mémoire virtuelle consommée par un processus est le total de tout ce qui se trouve dans la mémoire de 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 commande pmap pour voir toutes les choses mappées dans l'espace de 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 Les fenêtres). Voici un extrait de la carte mémoire du programme "Hello World"; toute la carte mémoire est longue de 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 mémoire virtuelle du segment. Cela est suivi par la taille du segment, les autorisations et la source du segment. Ce dernier élément est soit un fichier soit "anon", ce qui indique un bloc de mémoire alloué via mmap .

En partant du sommet, 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 charger dans les bibliothèques partagées où le vrai code JVM est stocké.
  • Un tas de blocs anon contenant le tas Java et les données internes. Il s'agit d'une JVM Sun, de sorte que le tas est divisé en plusieurs générations, chacune étant son propre bloc de mémoire. Notez que la machine virtuelle -Xmx alloue de l'espace mémoire virtuel 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 -Xms en cours d'utilisation au démarrage du programme et pour déclencher le -Xms la -Xms et à mesure que cette limite est atteinte.
  • Un fichier JAR mappé en mémoire, dans ce cas le fichier contenant les "classes JDK". Lorsque vous mappez un fichier JAR, vous pouvez accéder très efficacement aux fichiers qu'il contient (par opposition à chaque lecture depuis le début). La JVM Sun va mapper tous les fichiers JAR sur le chemin de classe; Si votre code d'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 douzaines sinon des centaines de ces entrées répétées à travers la carte mémoire.
  • Une des bibliothèques partagées qui contient le code JVM réel. Il y en a plusieurs.
  • La bibliothèque partagée pour la bibliothèque standard C. Ce n'est que l'une des nombreuses choses que la JVM charge 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 des données globales par processus pour la bibliothèque (je ne sais pas ce que segment sans autorisation est, je l'ai seulement vu sur Linux x64). 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 a 1.5M d'espace de mémoire virtuelle qui peut être partagé.

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

La carte mémoire virtuelle contient beaucoup de choses. Certains d'entre eux sont en lecture seule, d'autres sont partagés, et une partie est allouée mais jamais touchée (par exemple, presque tous les 4 Go de tas dans cet exemple). Mais le système d'exploitation est assez intelligent pour ne charger que ce dont il a besoin, donc 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 dans lequel vous ne pouvez allouer que 2 Go (ou, dans certains cas, 3Gb) d'espace d'adressage de processus. Dans ce cas, vous avez affaire à une ressource rare et devrez peut-être faire des compromis, par exemple réduire la taille de votre tas pour mapper un gros fichier ou créer beaucoup de threads.

Mais, étant donné que les machines 64 bits sont omniprésentes, je ne pense pas que cela prendra longtemps avant que la taille de la mémoire virtuelle soit une statistique complètement non pertinente.

Quand la taille du groupe résident est-elle importante?

La taille de l'ensemble résident est la partie de l'espace de mémoire virtuelle qui est réellement dans la RAM. Si votre flux RSS devient une partie importante de votre mémoire physique totale, il est peut-être temps de commencer à vous inquiéter. Si votre flux RSS prend toute votre mémoire physique et que votre système commence à s'échanger, il est grand temps de commencer à vous inquiéter.

Mais RSS est aussi trompeur, surtout sur une machine légèrement chargée. Le système d'exploitation ne dépense pas beaucoup d'efforts pour récupérer les pages utilisées par un processus. Il y a peu d'avantages à le faire, 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 de nombreuses pages qui ne sont pas utilisées activement.

Bottom Line

Sauf si vous échangez, ne soyez pas trop préoccupé par ce que les différentes statistiques de la mémoire vous disent. Avec la mise en garde qu'un RSS en croissance continue peut indiquer une sorte de fuite de mémoire.

Avec un programme Java, il est beaucoup plus important de prêter attention à ce qui se passe dans le tas. La quantité totale d'espace consommée est importante et vous pouvez prendre certaines mesures pour la réduire. Plus important est le temps que vous passez dans la récupération de place 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 pour l'autre, faites-le.





virtual-memory