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.
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
0 votes
Quelle implémentation de Java utilisez-vous ? IIRC, le Sun JRE libre et standard (non-OpenJDK) n'a pas de licence pour une utilisation embarquée.
0 votes
Je pense que j'ai mal utilisé le terme "embarqué"... la mémoire est limitée et le matériel est personnalisé, mais il s'agit toujours d'un ordinateur standard.
0 votes
Pour les systèmes limités/embarqués, j'ai découvert que je pouvais économiser de la mémoire en ajustant la variable -XX:MaxHeapFreeRatio, voir ma réponse ci-dessous pour plus de détails.
0 votes
@MarioOrtegón S'il s'agit d'un ordinateur "standard", c'est-à-dire capable d'exécuter une version officielle d'Oracle JDK/OpenJDK, il ne devrait pas y avoir de limite de mémoire virtuelle qui sorte de l'ordinaire. L'espace d'adressage disponible (mémoire virtuelle) est intégré au processeur et géré par le système d'exploitation. La mémoire virtuelle d'un processus n'interfère pas avec un autre processus. Bien sûr, la mémoire physique est une autre question.