Collecte des déchets en Java

La collecte des déchets en Java est l’un des sujets avancés. La connaissance du GC Java nous aide à peaufiner les performances d’exécution de notre application.

Collecte des déchets en Java

  • En Java, les programmeurs n’ont pas besoin de se soucier de détruire les objets qui ne sont plus utilisés. Le Ramasse-miettes s’en charge.
  • Le Ramasse-miettes est un thread Daemon qui s’exécute en arrière-plan. Fondamentalement, il libère la mémoire de la pile en détruisant les objets inaccessibles.
  • Les objets inaccessibles sont ceux qui ne sont plus référencés par aucune partie du programme.
  • Nous pouvons choisir le ramasse-miettes pour notre programme Java grâce aux options de JVM, nous examinerons cela dans la section suivante de ce tutoriel.

Comment fonctionne la collecte automatique des déchets ?

La collecte automatique des déchets est un processus consistant à examiner la mémoire du tas, à identifier (également appelé « marquage ») les objets inaccessibles et à les détruire par compactage. Un problème avec cette approche est que, à mesure que le nombre d’objets augmente, le temps de collecte des déchets augmente continuellement, car il doit parcourir toute la liste des objets à la recherche des objets inaccessibles. Cependant, l’analyse empirique des applications montre que la plupart des objets ont une durée de vie courte. Ce comportement a été utilisé pour améliorer les performances de la JVM, et la méthodologie adoptée est couramment appelée collecte des déchets générations. Dans cette méthode, l’espace du tas est divisé en générations telles que la jeune génération, la vieille génération ou génération tenurée, et la génération permanente. L’espace du tas de la jeune génération est celui où tous les nouveaux objets sont créés. Une fois qu’il est rempli, une collecte de déchets mineurs (également appelée GC mineur) a lieu. Cela signifie que tous les objets morts de cette génération sont détruits. Ce processus est rapide car, comme nous pouvons le voir sur le graphique, la plupart d’entre eux seraient morts. Les objets survivants de la jeune génération vieillissent et finissent par passer aux générations plus anciennes. La vieille génération est utilisée pour stocker des objets à longue durée de vie. En général, un seuil est défini pour l’objet de la jeune génération et lorsque cet âge est atteint, l’objet est déplacé vers la vieille génération. Finalement, la vieille génération doit être collectée. Cet événement est appelé un GC majeur (collecte de déchets majeure). Souvent, il est beaucoup plus lent car il implique tous les objets vivants. Il y a aussi le GC complet, ce qui signifie nettoyer tout le tas – à la fois les espaces de la jeune et de la vieille génération. Enfin, jusqu’à Java 7, il y avait une génération permanente (ou Perm Gen), qui contenait les métadonnées nécessaires à la JVM pour décrire les classes et méthodes utilisées dans l’application. Elle a été supprimée dans Java 8.

Collecteurs de déchets Java

Le JVM fournit en fait quatre collecteurs de déchets différents, tous générationnels. Chacun a ses avantages et inconvénients. Le choix du collecteur de déchets à utiliser nous revient et il peut y avoir des différences dramatiques de débit et de pauses d’application. Tous ceux-ci divisent le tas géré en différents segments, en utilisant les hypothèses séculaires selon lesquelles la plupart des objets dans le tas ont une durée de vie courte et doivent être recyclés rapidement. Ainsi, les quatre types de collecteurs de déchets sont :

GC en série

C’est le collecteur de déchets le plus simple, conçu pour les systèmes à un seul thread et une petite taille de tas. Il gèle toutes les applications pendant son fonctionnement. Peut être activé en utilisant l’option JVM -XX:+UseSerialGC.

GC parallèle/de débit

C’est le collecteur par défaut de la JVM dans JDK 8. Comme son nom l’indique, il utilise plusieurs threads pour balayer l’espace de la heap et effectuer une compactage. Un inconvénient de ce collecteur est qu’il met en pause les threads de l’application lors de l’exécution d’une collecte mineure ou complète. Il convient mieux aux applications capables de gérer de telles pauses et qui cherchent à optimiser la surcharge CPU causée par le collecteur.

Le collecteur CMS

L’algorithme du collecteur CMS (« concurrent-mark-sweep ») utilise plusieurs threads (« concurrents ») pour balayer la heap (« marquer ») à la recherche d’objets inutilisés pouvant être recyclés (« sweeper »). Ce collecteur entre en mode Stop-The-World (STW) dans deux cas : – lors de l’initialisation du marquage initial des racines, c’est-à-dire des objets dans la vieille génération accessibles à partir des points d’entrée du thread ou des variables statiques ; – lorsque l’application a modifié l’état de la heap pendant que l’algorithme s’exécutait de manière concurrente, l’obligeant à revenir en arrière pour apporter quelques touches finales afin de s’assurer qu’il a les bons objets marqués. Ce collecteur peut rencontrer des échecs de promotion. Si certains objets de la jeune génération doivent être déplacés vers la vieille génération et que le collecteur n’a pas eu suffisamment de temps pour libérer de l’espace dans la vieille génération, un échec de promotion se produira. Pour éviter cela, nous pouvons allouer plus d’espace de la heap à la vieille génération ou fournir plus de threads d’arrière-plan au collecteur.

Collecteur G1

Le dernier mais non le moindre est le collecteur Garbage-First, conçu pour les tailles de tas supérieures à 4 Go. Il divise la taille du tas en régions allant de 1 Mo à 32 Mo, en fonction de la taille du tas. Il y a une phase de marquage global concurrente pour déterminer la vitalité des objets dans tout le tas. Après la phase de marquage, G1 sait quelles régions sont principalement vides. Il collecte d’abord les objets inaccessibles de ces régions, ce qui génère généralement une grande quantité d’espace libre. Ainsi, G1 collecte d’abord ces régions (contenant des déchets), d’où le nom de Garbage-First. G1 utilise également un modèle de prédiction de pause afin de respecter un temps d’arrêt défini par l’utilisateur. Il sélectionne le nombre de régions à collecter en fonction du temps d’arrêt spécifié. Le cycle de collecte des déchets de G1 comprend les phases comme indiqué dans la figure :

  1. Phase réservée aux jeunes : Cette phase inclut uniquement les objets de la génération jeune et les promeut à la génération ancienne. La transition entre la phase réservée aux jeunes et la phase de récupération d’espace commence lorsque la génération ancienne est occupée jusqu’à un certain seuil, c’est-à-dire le seuil d’occupation initial du tas. À ce moment, G1 planifie une collecte réservée aux jeunes avec marquage initial au lieu d’une collecte régulière réservée aux jeunes.

  2. Marquage Initial : Ce type de collecte démarre le processus de marquage en plus d’une collecte régulière Jeunesse-unique. Le marquage simultané détermine tous les objets actuellement vivants dans les régions de la vieille génération à conserver pour la phase de récupération d’espace suivante. Pendant que le marquage n’est pas totalement terminé, des collectes régulières Jeunesse-unique peuvent se produire. Le marquage se termine par deux pauses spéciales stop-the-world : Remarque et Nettoyage.

  3. Remarque : Cette pause finalise le marquage lui-même et effectue le traitement global des références et le déchargement de classes. Entre la Remarque et le Nettoyage, G1 calcule un résumé de l’information de vivacité de manière simultanée, qui sera finalisé et utilisé dans la pause de Nettoyage pour mettre à jour les structures de données internes.

  4. Nettoyage : Cette pause prend également en compte les régions complètement vides et détermine si une phase de récupération d’espace suivra réellement. Si une phase de récupération d’espace suit, la phase jeune uniquement se termine par une seule collection jeune uniquement.

  5. Phase de récupération d’espace : Cette phase se compose de plusieurs collectes mixtes – en plus des régions de génération jeune, elle évacue également les objets vivants des régions de génération ancienne. La phase de récupération d’espace se termine lorsque G1 détermine qu’évacuer davantage de régions de génération ancienne ne produirait pas suffisamment d’espace libre pour justifier l’effort.

G1 peut être activé en utilisant le drapeau –XX:+UseG1GC. Cette stratégie réduit les risques d’épuisement du tas avant que les threads en arrière-plan aient terminé la numérisation des objets inaccessibles. De plus, elle compacte le tas en cours de route, ce que le collecteur CMS ne peut faire qu’en mode STW. Dans Java 8, une belle optimisation est fournie avec le collecteur G1, appelée déduplication de chaînes. Comme nous le savons, les tableaux de caractères qui représentent nos chaînes occupent une grande partie de notre espace de tas. Une nouvelle optimisation a été réalisée permettant au collecteur G1 d’identifier les chaînes qui sont dupliquées plus d’une fois dans notre tas et de les modifier pour pointer vers le même tableau de caractères interne, afin d’éviter la présence inutile de copies multiples de la même chaîne dans le tas. Nous pouvons utiliser l’argument JVM -XX:+UseStringDeduplication pour activer cette optimisation. G1 est le collecteur de déchets par défaut dans JDK 9.

Java 8 PermGen et Metaspace

Comme mentionné précédemment, l’espace de génération permanente a été supprimé depuis Java 8. Ainsi, le JDK 8 HotSpot JVM utilise maintenant la mémoire native pour la représentation des métadonnées de classe, appelée Metaspace. La plupart des allocations pour les métadonnées de classe sont effectuées à partir de la mémoire native. De plus, il existe un nouveau paramètre MaxMetaspaceSize pour limiter la quantité de mémoire utilisée pour les métadonnées de classe. Si nous ne spécifions pas de valeur pour cela, Metaspace se redimensionne à l’exécution selon les besoins de l’application en cours d’exécution. La collecte des déchets de Metaspace est déclenchée lorsque l’utilisation des métadonnées de classe atteint la limite de MaxMetaspaceSize. Une collecte excessive des déchets de Metaspace peut être un symptôme de fuite de mémoire des classes, des classloaders, ou d’une dimension insuffisante pour notre application. C’est tout pour la collecte des déchets en Java. J’espère que vous avez compris les différents collecteurs de déchets que nous avons en Java. Références : Documentation Oracle, G1 GC.

Source:
https://www.digitalocean.com/community/tutorials/garbage-collection-in-java