Blog

Le fonctionnement du garbage collector

Capture d’écran 2016-12-14 à 11.47.41
Share Button

Avertissement : cette page a vocation à expliquer dans les grandes lignes le fonctionnement du garbage collector (GC) de la JVM à l’intention des développeurs. Par conséquent je prends volontairement quelques raccourcis pour éviter d’écrire des tartines là-dessus, mais qui ne changent rien à la compréhension des principes du GC.


 

Débloque les + belles offres tech en 10 mins

D’autre part, parce que cet article est avant tout destiné aux développeurs, je vais passer totalement sur le tuning du GC qui ne doit de toute façon servir qu’en dernier recours hormis pour quelques paramètres. En effet, on ne répare pas un code moisi en tunant une plateforme. Si vous voulez approfondir sur tout ça, je vous invite vivement à lire l’ouvrage Java performance.

Le Garbage Collector (GC) est implémenté depuis les débuts de la JVM. Son rôle consiste en fait à gérer en grande partie la mémoire à votre place. C’est pourquoi en Java il n’y a pas besoin de faire des delete de pointeurs comme en C par exemple. Cela dit le GC ne fait pas tout non plus, par exemple si vous conservez une liste en mémoire, que vous remplissez perpétuellement sans jamais la vider, au bout d’un moment vous obtiendrez quand même une fuite mémoire.

Le fonctionnement du GC dans les grandes lignes

En Java, ce qu’on voit comme objets sont en fait des références vers ces objets. Chacun de ces objets contient de manière sous-jacente un compteur de références, qui sert à déterminer si un objet peut être supprimé ou non de la mémoire. Le fonctionnement est assez simple : à chaque fois qu’une nouvelle référence à l’objet apparaît, le compteur est incrémenté. Inversement, quand une référence vers un objet est supprimée, le compteur est décrémenté. Quand celui-ci est à zéro, cela signifie qu’au prochain passage du GC cet objet sera supprimé de la mémoire.

Lorsque trop d’objets ont été alloués sans être libérés, la JVM arrive à court de mémoire et invoque le GC pour en libérer. Celui-ci va alors faire une passe pour supprimer les objets dont le compteur de référence est à zéro. Des théories ont toutefois montré qu’une grosse majorité des objets d’une application ne vivait pas longtemps, autrement dit leur compteur de références tombait très vite à zéro. Pour des raisons de performance, la mémoire vue par le GC a donc été divisée en deux zones : le YoungGen, qui stocke les objets récents, et l’OldGen, qui stocke les objets à durée de vie longue. Il existe par ailleurs le PermGen sur lequel nous passons car il va disparaître avec Java 8. Un tel système permet de purger fréquemment les objets jeunes, ce qui est une opération peu coûteuse, et de ne purger que nettement plus rarement les vieux objets, car c’est très coûteux.

L' »âge » d’un objet est déterminé en fonction du nombre de cycles de GC auxquels il a « survécu ». De la même manière que chacun des objets comporte un compteur de référence, il possède également un compteur de cycles de GC. Ce dernier est incrémenté à chaque fois qu’il survit à un GC, et au bout d’un certain nombre de passages l’objet est transféré du YoungGen vers le OldGen.

GC vs. Full GC

Comme nous l’avons vu, le GC divise la mémoire entre la YoungGen et l’OldGen. Lorsque la mémoire YoungGen est pleine une Garbage Collection (GC) survient, et les actions suivantes sont effectuées :

  1. Supprimer les objets de la YoungGen dont le compteur de références est tombé à zéro.
  2. Incrémenter le compteur de GC des objets survivants.
  3. Déplacer les objets dont le compteur de GC a dépassé un certain seuil vers l’OldGen.

Cette opération est très rapide, de l’ordre de quelques millisecondes, et comme le YoungGen est assez petit elle s’exécute assez fréquemment.

Lorsque c’est l’OldGen qui est plein, une Full Garbage Collection (Full GC) survient. Celle-ci consiste à faire le travail d’un GC normal, mais également à supprimer de la zone OldGen tous les objets dont le compteur de références a atteint zéro. Il s’agit d’une opération coûteuse, pouvant durer plusieurs dizaines de secondes dans le pire des cas. Pendant ce temps votre application est ralentie, ou peut être complètement figée, suivant l’algorithme de GC choisi. Nous ne détaillerons pas ce dernier point dans l’article.

 

Et en quoi ça m’impacte, moi, développeur ?

Tout d’abord, un développeur peut déclencher un GC en appelant System.gc(). La spécification de Java indique que cette méthode envoie un message au GC indiquant qu’il serait bon qu’il se déclenche. Mais dans l’implémentation de référence de la JVM cette méthode déclenche dans les faits un full GC. Il vaut donc mieux éviter de l’utiliser, et dans ma vie de développeur ça ne m’est arrivé qu’une seule fois pour contourner un bug d’une librairie dont je n’avais pas le code.

L’autre implication est qu’il convient de ne pas allouer plus d’objets que nécessaire dans vos programmes, par exemple en initialisant plusieurs objets avec la même valeur au sein d’une même méthode pour les jeter immédiatement après. En effet à terme ceci déclenche de nombreux GC, et accélère le « vieillissement » de vos objets en les déplaçant prématurément dans la OldGen. Essayez donc plutôt de recycler vos objets. Pour une Collection, il vaut généralement mieux appeler la méthode clear() plutôt que d’initialiser une nouvelle collection du même type mais vide.

Pour vous donner une idée de ce que peut donner une optimisation de l’utilisation de la mémoire, dans mon jeu de Puissance 4, entre les versions 1.3.1 et 1.3.6.1 j’ai appliqué une méthode m’évitant de faire trop de copies en mémoire du tableau de jeu lorsque l’IA était utilisée. J’ai ainsi gagné… 90% de performances !

Mise à jour : comme indiqué dans les commentaires le GC en Java fonctionne sur un système de graphes d’objets et non de compteur de références, ce qui permet notamment de résoudre le problème des cycles. Bref ça fait un peu d’algo des graphes. ;-) Ca n’empêche que du point de vue du développeur ça ne change pas grand chose, ce qui compte est de savoir l’utiliser correctement de façon à ce qu’il n’occupe pas plus de 5% du temps machine de votre programme.
 
 

Besoin de tester ton niveau en développement informatique ?

Choisis ton test technique en quelques secondes parmi + 40 langages/frameworks puis évalue rapidement ton niveau en ligne, grâce à une équipe d’experts. Rejoins-les vite :
Guillaume_Brout Olivier_Milla Jean-Marie_Clery Khalid_Jebbari Peter _Stavrinides Alex_Bystritskiy Urs_RoeschAbha_Agrawa

Je teste mon niveau en développement maintenant !

 
 

Cet article vous a plu ? Vous aimerez sûrement aussi :

 

JulienJulien
Moi c’est Julien, ingénieur en informatique avec quelques années d’expérience. Je suis tombé dans la marmite étant petit, mon père avait acheté un Apple – avant même ma naissance (oui ça date !). Et maintenant je me passionne essentiellement pour tout ce qui est du monde Java et du système, les OS open source en particulier.

Au quotidien, je suis devops, bref je fais du dév, je discute avec les opérationnels, et je fais du conseil auprès des clients.

Son Twitter - Son LinkedIn

Share Button

3 commentaires

  • Yannis dit :

    N’importe quoi… Le GC de Java n’utilise pas de compteurs de références. Ce genre de technique ne supporte pas les références circulaires.

    • gojul dit :

      Bonjour,

      Le compteur de références fait partie des techniques utilisées par les GC pour détecter les objets inutilisés. Après il n’y a pas que ça non plus, notamment pour les références circulaires où on utilise les tracing GCs basés sur l’algorithmique des graphes, mais ça en fait partie. D’autant qu’il s’agit de l’implémentation historique des GC, qui permet de comprendre facilement leur fonctionnement (même si maintenant ça a changé, là-dessus on est d’accord, mais le but de l’article est avant tout d’être didactique sur le sujet, quitte à avoir quelques imprécisions sur l’implémentation réelle cf. ma remarque en introduction) L’intérêt pour le développeur est principalement de savoir programmer en évitant de solliciter trop le GC, lequel ne devrait pas occuper plus de 5% du temps de l’exécution d’un programme. Et encore même 5% c’est énorme.

      Si vous ne désirez pas lire le livre que j’ai indiqué vous pouvez le voir sur Wikipedia : http://en.wikipedia.org/wiki/Reference_counting

      Bien à vous

  • Yannis dit :

    « cette page a vocation à expliquer dans les grandes lignes le fonctionnement du garbage collector (GC) de la JVM » : il faudrait enlever cette ligne et alors parler des GC des années 80 dans Visual BASIC, un truc comme ça, de mémoire.

    Je comprends bien le principe « je simplifie histoire de rester abordable » mais bon, là, je n’appelle pas ça de la simplification.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>