Il s'agit d'une plainte de longue date avec Java, mais elle est largement dénuée de sens, et généralement basée sur la recherche des informations erronées. Le phrasé habituel est quelque chose comme "Hello World sur Java prend 10 mégaoctets! Pourquoi a-t-il besoin de ça?" Eh bien, voici un moyen de faire Hello World sur une JVM 64 bits prétendre 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 top vous donne plusieurs nombres différents pour la mémoire. Voici ce qu'il dit à propos de l'exemple Hello World:
UTILISATEUR PID 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). Il est largement dénué de sens, sauf lorsqu'il ne l'est pas (voir ci-dessous).
- RES est la taille de l'ensemble résident: le nombre de pages qui résident actuellement dans la RAM. Dans presque tous les cas, c'est le seul numéro que vous devez 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, 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 soupçonne 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'est pas affiché ici. Il indique la quantité de mémoire virtuelle qui réside actuellement sur le disque, qu'elle se trouve ou non dans l'espace d'échange . Le système d'exploitation est très bon pour conserver les pages actives dans la RAM, et les seuls remèdes 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 de 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 précise pas ce qu'elles signifient. Windows Vista et Windows 7 ajoutent plus de colonnes, et elles sont en fait documentées . Parmi celles-ci, la mesure "Working Set" est la plus utile; il 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 est le total de tout ce qui se trouve dans la carte de mémoire de processus. Cela inclut les données (par exemple, le tas 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 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"; l'ensemble de la carte mémoire comporte plus de 100 lignes, et il n'est pas rare d'avoir une liste de mille lignes.
0000000040000000 36K rx-- /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 rx-- /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 rx-- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3634000 2044K ----- /lib/x86_64-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 mémoire virtuelle du segment. Ceci est suivi de la taille du segment, des autorisations et de 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; il ne fait que 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 machine virtuelle Java Sun. Le tas est donc divisé en plusieurs générations, chacune étant son propre bloc de mémoire. Notez que la JVM alloue de l'espace mémoire virtuel en fonction de la
-Xmx
valeur; cela lui permet d'avoir un tas contigu. La -Xms
valeur est utilisée en interne pour indiquer la quantité de tas "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 qui contient les "classes JDK". Lorsque vous mappez en mémoire un fichier JAR, vous pouvez accéder aux fichiers qu'il contient de manière très efficace (au lieu de le lire chaque fois depuis le début). La JVM Sun mappe en mémoire tous les fichiers JAR du chemin de classe; 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 1M est la pile de threads. Je n'avais pas une bonne explication pour le bloc 4k, mais @ericsoe l'a identifié comme un "bloc de garde": il n'a pas d'autorisations de lecture / écriture, donc causera une erreur de segment s'il est accédé, et la JVM le capture et le traduit à un
StackOverFlowError
. Pour une vraie application, vous verrez des dizaines sinon des centaines de ces entrées répétées à travers la carte mémoire.
- L'une des bibliothèques partagées contenant le code JVM réel. Il y en a plusieurs.
- La bibliothèque partagée pour la bibliothèque standard C. Ceci 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 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 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
dispose de 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 de mémoire virtuelle contient beaucoup de choses. Une partie est en lecture seule, une partie est partagée et une partie est allouée mais jamais touchée (par exemple, presque tout le 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 largement hors de propos.
Lorsque la taille de la mémoire virtuelle est importante, si vous utilisez un système d'exploitation 32 bits, 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 devrez peut-être faire des compromis, comme réduire la taille du segment de mémoire 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 complètement hors de propos.
Quand la taille définie par le résident est-elle importante?
La taille de l'ensemble résident est la partie de l'espace mémoire virtuelle 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 flux RSS prend toute votre mémoire physique et que votre système commence à échanger, il est grand temps de commencer à vous inquiéter.
Mais RSS est également trompeur, en particulier sur une machine légèrement chargée. Le système d'exploitation ne consacre pas beaucoup d'efforts à 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
À moins que vous n'échangiez, ne vous inquiétez pas trop de ce que les différentes statistiques de la mémoire vous disent. Avec la mise en garde qu'un RSS toujours croissant peut indiquer une sorte de fuite de mémoire.
Avec un programme Java, il est beaucoup plus important de faire attention à ce qui se passe dans le tas. La quantité totale d'espace consommé est importante et vous pouvez prendre certaines mesures pour réduire cela. Plus important est le temps que vous passez dans la collecte des ordures et quelles parties du tas sont collectées.
L'accès au disque (c'est-à-dire à une base de données) coûte cher et la mémoire est bon marché. Si vous pouvez échanger l'un contre l'autre, faites-le.