La compréhension du Modèle de Mémoire JVM, de la Gestion de la Mémoire Java est très importante si vous souhaitez comprendre le fonctionnement de la Collecte des Déchets Java. Aujourd’hui, nous examinerons la gestion de la mémoire en Java, les différentes parties de la mémoire JVM et comment surveiller et effectuer l’optimisation de la collecte des déchets.
Modèle de Mémoire Java (JVM)
Comme vous pouvez le voir dans l’image ci-dessus, la mémoire JVM est divisée en parties distinctes. À un niveau élevé, la mémoire de la JVM est physiquement divisée en deux parties – la Génération Jeune et la Génération Ancienne.
Gestion de la Mémoire en Java – Génération Jeune
La génération jeune est l’endroit où tous les nouveaux objets sont créés. Lorsque la génération jeune est remplie, une collecte des déchets est effectuée. Cette collecte des déchets est appelée GC Mineur. La Génération Jeune est divisée en trois parties – la Mémoire Eden et deux espaces de Mémoire Survivor. Points Importants sur les Espaces de la Génération Jeune :
- La plupart des objets nouvellement créés sont situés dans l’espace mémoire Eden.
- Lorsque l’espace Eden est rempli d’objets, une GC mineure est effectuée et tous les objets survivants sont déplacés vers l’un des espaces survivants.
- La GC mineure vérifie également les objets survivants et les déplace vers l’autre espace survivant. Ainsi, à un moment donné, l’un des espaces survivants est toujours vide.
- Les objets qui survivent après de nombreux cycles de GC sont déplacés vers l’espace mémoire de la génération ancienne. Habituellement, cela est fait en définissant un seuil pour l’âge des objets de la génération jeune avant qu’ils ne deviennent éligibles pour être promus à la génération ancienne.
Gestion de la mémoire en Java – Génération ancienne
La mémoire de la génération ancienne contient les objets qui ont une longue durée de vie et qui ont survécu après de nombreux cycles de GC mineure. Habituellement, la collecte des ordures est effectuée dans la mémoire de la génération ancienne lorsqu’elle est pleine. La collecte des ordures de la génération ancienne est appelée GC majeure et prend généralement plus de temps.
Événement Stop the World
Toutes les collections d’ordures sont des événements « Stop the World » car tous les threads d’application sont arrêtés jusqu’à ce que l’opération soit terminée. Comme la génération Young contient des objets de courte durée de vie, la GC mineure est très rapide et l’application n’en est pas affectée. Cependant, la GC majeure prend beaucoup de temps car elle vérifie tous les objets vivants. La GC majeure devrait être minimisée car elle rendrait votre application non réactive pendant la durée de la collecte des déchets. Ainsi, si vous avez une application réactive et qu’il y a beaucoup de collections majeures de déchets, vous remarquerez des erreurs de délai d’attente. La durée prise par le collecteur de déchets dépend de la stratégie utilisée pour la collecte des déchets. C’est pourquoi il est nécessaire de surveiller et d’ajuster le collecteur de déchets pour éviter les délais d’attente dans les applications hautement réactives.
Modèle de mémoire Java – Génération permanente
La génération permanente ou « Perm Gen » contient les métadonnées de l’application requises par la JVM pour décrire les classes et les méthodes utilisées dans l’application. Notez que Perm Gen ne fait pas partie de la mémoire Java Heap. Perm Gen est peuplé par la JVM au moment de l’exécution en fonction des classes utilisées par l’application. Perm Gen contient également des classes et des méthodes de bibliothèque Java SE. Les objets Perm Gen sont collectés par la collecte complète des déchets.
Modèle de mémoire Java – Zone de méthode
La zone de méthode fait partie de l’espace dans Perm Gen et est utilisée pour stocker la structure de classe (constantes d’exécution et variables statiques) et le code des méthodes et des constructeurs.
Modèle de mémoire Java – Pool de mémoire
Les pools de mémoire sont créés par les gestionnaires de mémoire JVM pour créer un pool d’objets immuables si l’implémentation le prend en charge. Le pool de chaînes est un bon exemple de ce type de pool de mémoire. Le pool de mémoire peut appartenir à Heap ou à Perm Gen, selon l’implémentation du gestionnaire de mémoire JVM.
Modèle de mémoire Java – Pool de constantes d’exécution
Le pool de constantes d’exécution est la représentation d’exécution par classe du pool de constantes dans une classe. Il contient les constantes d’exécution de classe et les méthodes statiques. Le pool de constantes d’exécution fait partie de la zone de méthode.
Modèle de mémoire Java – Mémoire de la pile Java
La mémoire de la pile Java est utilisée pour l’exécution d’un thread. Elle contient des valeurs spécifiques à la méthode qui sont de courte durée et des références à d’autres objets dans le tas qui sont référencés à partir de la méthode. Vous devriez lire Différence entre la mémoire de la pile et la mémoire du tas.
Gestion de la mémoire en Java – Commutateurs de mémoire du tas Java
Java fournit beaucoup de commutateurs de mémoire que nous pouvons utiliser pour définir les tailles de mémoire et leurs ratios. Certains des commutateurs de mémoire couramment utilisés sont :
VM Switch | VM Switch Description |
---|---|
-Xms | For setting the initial heap size when JVM starts |
-Xmx | For setting the maximum heap size. |
-Xmn | For setting the size of the Young Generation, rest of the space goes for Old Generation. |
-XX:PermGen | For setting the initial size of the Permanent Generation memory |
-XX:MaxPermGen | For setting the maximum size of Perm Gen |
-XX:SurvivorRatio | For providing ratio of Eden space and Survivor Space, for example if Young Generation size is 10m and VM switch is -XX:SurvivorRatio=2 then 5m will be reserved for Eden Space and 2.5m each for both the Survivor spaces. The default value is 8. |
-XX:NewRatio | For providing ratio of old/new generation sizes. The default value is 2. |
La plupart du temps, les options ci-dessus sont suffisantes, mais si vous voulez vérifier d’autres options également, veuillez consulter la Page officielle des options JVM.
Gestion de la mémoire en Java – Collecte des déchets Java
La collecte des déchets Java est le processus visant à identifier et à supprimer les objets inutilisés de la mémoire et à libérer de l’espace pour être alloué aux objets créés dans le traitement ultérieur. L’une des meilleures fonctionnalités du langage de programmation Java est la collecte automatique des déchets, contrairement à d’autres langages de programmation tels que le C où l’allocation et la libération de mémoire sont un processus manuel. Le collecteur de déchets est le programme s’exécutant en arrière-plan qui examine tous les objets en mémoire et trouve les objets qui ne sont référencés par aucune partie du programme. Tous ces objets non référencés sont supprimés et l’espace est récupéré pour être alloué à d’autres objets. L’une des méthodes de base de la collecte des déchets implique trois étapes :
- Marquage : C’est la première étape où le collecteur de déchets identifie quels objets sont en cours d’utilisation et lesquels ne le sont pas.
- Suppression normale : Le collecteur de déchets supprime les objets inutilisés et récupère l’espace libre pour être alloué à d’autres objets.
- Suppression avec compactage : Pour de meilleures performances, après la suppression des objets inutilisés, tous les objets survivants peuvent être déplacés ensemble. Cela augmentera les performances de l’allocation de mémoire aux nouveaux objets.
Il existe deux problèmes avec une approche simple de marquage et de suppression.
- Premièrement, ce mécanisme n’est pas efficient car la plupart des objets nouvellement créés ne seront jamais utilisés
- . Deuxièmement, les objets qui sont en utilisation pendant plusieurs cycles de collecte des ordures sont très susceptibles d’être utilisés également dans les cycles futurs
. Les défauts précités de l’approche simple expliquent pourquoi la collecte des ordures de Java est générationnelle et que nous avons des espaces jeune génération et vieille génération dans la mémoire du tas. J’ai déjà expliqué plus haut comment les objets sont examinés et déplacés d’un espace de génération à un autre en fonction du Mini-GC et du Major-GC.
Gestion de la mémoire en Java – Types de collecte des ordures de Java
Il existe cinq types de stratégies de collecte des ordures que nous pouvons utiliser dans nos applications. Il suffit d’utiliser le commutateur JVM pour activer la stratégie de collecte des ordures pour l’application. Voyons-les un par un.
- GC sérieux (-XX:+UseSerialGC) : le GC sérieux utilise l’approche simple marque-sweep-compact pour la collecte des ordures des générations jeune et vieille, c’est-à-dire le Mini-GC et le Major-GC. Le GC sérieux est utile sur les ordinateurs clients tels que nos applications独立antes et les machines avec un CPU plus petit. Il est bon pour les petites applications avec une empreinte mémoire faible.
- GC parallèle (-XX:+UseParallelGC): Le GC parallèle est identique au GC en série, sauf qu’il crée N threads pour la collecte des déchets de la génération jeune, où N est le nombre de cœurs de processeur dans le système. Nous pouvons contrôler le nombre de threads en utilisant l’option JVM
-XX:ParallelGCThreads=n
. Le collecteur de déchets parallèle est également appelé collecteur de débit car il utilise plusieurs processeurs pour accélérer les performances du GC. Le GC parallèle utilise un seul thread pour la collecte des déchets de la génération ancienne. - GC parallèle ancien (-XX:+UseParallelOldGC): C’est la même chose que le GC parallèle, sauf qu’il utilise plusieurs threads pour la collecte des déchets à la fois pour la génération jeune et la génération ancienne.
- Collecteur de balayage simultané (CMS) (-XX:+UseConcMarkSweepGC): Le collecteur CMS est également appelé collecteur de faible latence concurrente. Il effectue la collecte des déchets pour la génération ancienne. Le collecteur CMS tente de minimiser les pauses dues à la collecte des déchets en effectuant la plupart du travail de collecte des déchets en parallèle avec les threads de l’application. Le collecteur CMS sur la génération jeune utilise le même algorithme que celui du collecteur parallèle. Ce collecteur de déchets est adapté aux applications réactives où nous ne pouvons pas nous permettre des temps d’arrêt plus longs. Nous pouvons limiter le nombre de threads dans le collecteur CMS en utilisant l’option JVM
-XX:ParallelCMSThreads=n
. - Collecteur de déchets G1 (-XX:+UseG1GC): Le collecteur de déchets Garbage First ou G1 est disponible à partir de Java 7 et son objectif à long terme est de remplacer le collecteur CMS. Le collecteur G1 est un collecteur de déchets parallèle, concurrent et compactage incrémentiel à faible pause. Le collecteur Garbage First ne fonctionne pas comme les autres collecteurs et il n’y a pas de concept d’espace de génération Young et Old. Il divise l’espace de la pile en plusieurs régions de pile de taille égale. Lorsqu’une collecte de déchets est invoquée, elle collecte d’abord la région avec moins de données vivantes, d’où le nom « Garbage First ». Vous pouvez trouver plus de détails à ce sujet dans la Documentation Oracle sur le collecteur Garbage-First.
Gestion de la mémoire en Java – Surveillance de la collecte de déchets Java
Nous pouvons utiliser la ligne de commande Java ainsi que des outils d’interface utilisateur pour surveiller les activités de collecte des déchets d’une application. Pour mon exemple, j’utilise l’une des applications de démonstration fournies par les téléchargements Java SE. Si vous souhaitez utiliser la même application, rendez-vous sur la page Téléchargements Java SE et téléchargez JDK 7 et JavaFX Démos et Exemples. L’application de démonstration que j’utilise est Java2Demo.jar et elle se trouve dans le répertoire jdk1.7.0_55/demo/jfc/Java2D
. Cependant, il s’agit d’une étape facultative et vous pouvez exécuter les commandes de surveillance du GC pour n’importe quelle application Java. La commande que j’utilise pour démarrer l’application de démonstration est :
pankaj@Pankaj:~/Downloads/jdk1.7.0_55/demo/jfc/Java2D$ java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar Java2Demo.jar
jstat
Nous pouvons utiliser l’outil en ligne de commande jstat
pour surveiller la mémoire JVM et les activités de collecte des déchets. Il est livré avec le JDK standard, donc vous n’avez rien d’autre à faire pour l’obtenir. Pour exécuter jstat
, vous devez connaître l’identifiant de processus de l’application, que vous pouvez obtenir facilement en utilisant la commande ps -eaf | grep java
.
pankaj@Pankaj:~$ ps -eaf | grep Java2Demo.jar
501 9582 11579 0 9:48PM ttys000 0:21.66 /usr/bin/java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseG1GC -jar Java2Demo.jar
501 14073 14045 0 9:48PM ttys002 0:00.00 grep Java2Demo.jar
Donc l’identifiant de processus pour mon application Java est 9582. Maintenant, nous pouvons exécuter la commande jstat comme indiqué ci-dessous.
pankaj@Pankaj:~$ jstat -gc 9582 1000
S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT
1024.0 1024.0 0.0 0.0 8192.0 7933.3 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8026.5 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8030.0 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8122.2 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8171.2 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 48.7 0.0 8192.0 106.7 42108.0 23401.3 20480.0 19990.9 158 0.275 40 1.381 1.656
1024.0 1024.0 48.7 0.0 8192.0 145.8 42108.0 23401.3 20480.0 19990.9 158 0.275 40 1.381 1.656
Le dernier argument pour jstat est l’intervalle de temps entre chaque sortie, de sorte qu’il affichera les données de la mémoire et de la collecte des déchets toutes les 1 seconde. Parcourons chacune des colonnes une par une.
- S0C et S1C : Cette colonne montre la taille actuelle des zones Survivor0 et Survivor1 en Ko.
S0U et S1U : Cette colonne montre l’utilisation actuelle des zones Survivor0 et Survivor1 en Ko. Remarquez qu’une des zones de survie est toujours vide. - S0U et S1U: Cette colonne indique l’utilisation actuelle des zones Survivor0 et Survivor1 en KB. Notez qu’une des zones survivantes est toujours vide.
- EC et EU: Ces colonnes indiquent la taille actuelle et l’utilisation de l’espace Eden en KB. Notez que la taille EU augmente et dès qu’elle dépasse l’EC, une GC mineure est appelée et la taille EU est réduite.
- OC et OU: Ces colonnes indiquent la taille actuelle et l’utilisation actuelle de la génération Old en KB.
- PC et PU: Ces colonnes indiquent la taille actuelle et l’utilisation actuelle de la génération Perm en KB.
- YGC et YGCT: La colonne YGC affiche le nombre d’événements GC survenus dans la génération jeune. La colonne YGCT affiche le temps cumulé des opérations GC pour la génération jeune. Notez qu’ils augmentent tous les deux dans la même ligne où la valeur EU chute à cause d’une GC mineure.
- FGC et FGCT: La colonne FGC affiche le nombre d’événements GC complets survenus. La colonne FGCT affiche le temps cumulé des opérations GC complètes. Notez que le temps GC complet est beaucoup trop élevé par rapport aux temps de génération jeune.
- GCT: Cette colonne affiche le temps cumulé total des opérations GC. Notez qu’il s’agit de la somme des valeurs des colonnes YGCT et FGCT.
L’avantage de jstat est qu’il peut être exécuté sur des serveurs distants où nous n’avons pas de GUI. Notez que la somme de S0C, S1C et EC est de 10m comme spécifié via l’option JVM -Xmn10m
.
Java VisualVM avec Visual GC
Si vous souhaitez visualiser la mémoire et les opérations de GC dans une interface graphique, vous pouvez utiliser l’outil jvisualvm
. Java VisualVM fait également partie du JDK, donc vous n’avez pas besoin de le télécharger séparément. Il suffit d’exécuter la commande jvisualvm
dans le terminal pour lancer l’application Java VisualVM. Une fois lancé, vous devez installer le plugin Visual GC depuis l’option Outils -< Plugins, comme indiqué dans l’image ci-dessous. Après avoir installé Visual GC, ouvrez simplement l’application dans la colonne de gauche et accédez à la section Visual GC. Vous obtiendrez une image de la mémoire JVM et des détails sur la collecte des déchets, comme indiqué dans l’image ci-dessous.
Ajustement de la collecte des déchets Java
L’optimisation de la collecte des ordures Java devrait être la dernière option que vous devriez utiliser pour augmenter le débit de votre application et seulement lorsque vous constatez une baisse de performance en raison de durées de GC plus longues causant des délais d’application. Si vous voyez des erreurs java.lang.OutOfMemoryError: Espace PermGen
dans les journaux, essayez de surveiller et d’augmenter l’espace mémoire Perm Gen en utilisant les options JVM -XX:PermGen et -XX:MaxPermGen. Vous pouvez également essayer d’utiliser -XX:+CMSClassUnloadingEnabled
et vérifier comment cela fonctionne avec le collecteur de déchets CMS. Si vous observez beaucoup d’opérations de Full GC, vous devriez essayer d’augmenter l’espace mémoire de la génération ancienne. Globalement, l’optimisation de la collecte des ordures demande beaucoup d’efforts et de temps et il n’y a pas de règle immuable à ce sujet. Vous devrez essayer différentes options et les comparer pour trouver celle qui convient le mieux à votre application. C’est tout pour le modèle de mémoire Java, la gestion de la mémoire en Java et la collecte des ordures, j’espère que cela vous aide à comprendre la mémoire JVM et le processus de collecte des ordures.
Source:
https://www.digitalocean.com/community/tutorials/java-jvm-memory-model-memory-management-in-java