CodeGym /Cours /JAVA 25 SELF /Ramasse-miettes : G1, ZGC, Shenandoah, comparaison

Ramasse-miettes : G1, ZGC, Shenandoah, comparaison

JAVA 25 SELF
Niveau 64 , Leçon 1
Disponible

1. Introduction au ramasse-miettes (GC)

Si vous avez déjà programmé en C ou C++, vous avez certainement dû libérer la mémoire manuellement avec free() ou delete. En Java, c’est beaucoup plus simple : vous créez un objet avec new, et vous n’avez pas besoin de le supprimer — c’est un « concierge » spécial, appelé ramasse-miettes (Garbage Collector, GC), qui s’en charge.

Le GC est une partie de la JVM qui libère automatiquement la mémoire occupée par les objets auxquels il n’existe plus de références. Grâce à lui, les développeurs Java n’ont pas à craindre d’oublier de libérer la mémoire (et de provoquer une fuite), ni de supprimer par erreur un objet encore nécessaire (et de provoquer un crash).

Mais, comme tout concierge, le GC n’est pas parfait : il peut parfois intervenir au pire moment en faisant un « grand ménage » (Stop-the-World), ou ne pas être aussi rapide qu’on le souhaiterait. C’est pourquoi la JVM propose plusieurs implémentations différentes du ramasse-miettes — et le bon choix peut influencer sensiblement les performances de l’application.

Types principaux de ramasse-miettes

Serial GC

  • Serial GC — le collecteur le plus simple et le plus ancien.
  • Fonctionne sur un seul thread.
  • Arrête tous les autres threads pendant la collecte (Stop-the-World).
  • Adapté aux petites applications sans multithreading intensif.
  • Activé avec l’option : -XX:+UseSerialGC

Parallel GC

  • Parallel GC (également « Throughput Collector »).
  • Utilise plusieurs threads pour collecter les déchets.
  • Orienté vers un débit maximal.
  • Effectue toujours la collecte avec des pauses Stop-the-World, mais plus rapidement que Serial.
  • Convient aux applications serveur où de petites pauses ne sont pas critiques.
  • Activé avec l’option : -XX:+UseParallelGC

CMS (Concurrent Mark Sweep)

  • CMS — obsolète mais longtemps populaire, minimisant les pauses.
  • Travaille partiellement en parallèle avec l’application, réduisant le temps d’arrêt.
  • Plus complexe à régler, avec des surcoûts.
  • Marqué obsolète (deprecated) depuis Java 9.
  • Activé avec l’option : -XX:+UseConcMarkSweepGC

G1 (Garbage First)

  • G1 GC — collecteur moderne utilisé par défaut (à partir de Java 9).
  • Équilibre pauses minimales et performance.
  • Divise le tas en de nombreux petits régions (modèle régional).
  • Peut collecter sélectivement par régions, sans toucher tout le tas.
  • Permet de fixer une pause maximale cible, par exemple -XX:MaxGCPauseMillis=200.
  • Option d’activation : -XX:+UseG1GC (généralement inutile, car G1 est par défaut).

ZGC et Shenandoah

  • ZGC et Shenandoah — ramasse-miettes modernes à faible latence.
  • Objectif : pauses minimales (millisecondes), même sur de très grands tas (jusqu’à des téraoctets).
  • Fonctionnent quasiment entièrement en parallèle avec l’application.
  • Nécessitent Java 11+ (ZGC) ou Java 12+ (Shenandoah).
  • Conviennent aux systèmes sensibles à la latence (bourses, fintech, analytique temps réel).
  • Options d’activation : -XX:+UseZGC ou -XX:+UseShenandoahGC

3. Principes de fonctionnement des GC modernes

Jeune et vieille génération (Young/Old Generation)

La JVM divise le tas en deux grandes parties :

Jeune génération (Young Generation) : tous les nouveaux objets y arrivent. La collecte y est fréquente et rapide ( Minor GC ).

Vieille génération (Old Generation, Tenured) : les objets qui ont « survécu » à plusieurs collectes dans la jeune génération y migrent. La collecte y est plus rare mais plus longue ( Major/Full GC ).

Pourquoi ? La majorité des objets en Java vivent très peu de temps (par exemple, des chaînes temporaires, des collections internes à une méthode). On peut donc collecter rapidement et souvent la jeune génération sans toucher à l’ancienne.

Minor GC

  • Nettoie uniquement la jeune génération.
  • Rapide, avec une courte pause.
  • Ne touche pas aux objets anciens.

Major (Full) GC

  • Nettoie tout le tas (jeune et vieille génération).
  • Peut prendre beaucoup de temps (des secondes et plus sur de grands tas).
  • S’accompagne généralement d’une longue pause de l’application.

Comment le GC détermine quels objets supprimer ?

Le GC recherche les objets « vivants » en partant des références racines (root set) : variables locales dans les piles des threads, champs statiques, paramètres de méthodes, etc. Tout ce qui est « atteignable » est considéré comme vivant. Le reste — des déchets.

4. Comparaison des collecteurs modernes : G1, ZGC, Shenandoah

Voyons en quoi diffèrent les GC les plus modernes et populaires. Pour cela, un tableau récapitulatif :

Collecteur Objectif principal Modèle mémoire Pauses minimales Scalabilité Support Quand l’utiliser
G1 Équilibre pauses/vitesse Régions ~10–200 ms Jusqu’à des centaines de Go Java 9+ (par défaut) La plupart des applications serveur
ZGC Pause minimale Régions, « marquage coloré » <10 ms Jusqu’à des To Java 11+ Temps réel, sensible à la latence
Shenandoah Pause minimale Régions, « marquage coloré » <10 ms Jusqu’à des To Java 12+ (Red Hat) Temps réel, sensible à la latence

G1 GC : Garbage First

  • Divise le tas en de nombreuses régions (généralement 1–32 Mo chacune).
  • Lors de la collecte, choisit les régions avec le plus de déchets (« garbage first »).
  • Peut collecter seulement une partie du tas, et non tout d’un coup.
  • Permet de définir une pause cible : -XX:MaxGCPauseMillis=200.
  • Convient pour équilibrer vitesse et pauses ; utilisé par défaut depuis Java 9.

Exemple d’activation (si jamais désactivé) :

java -XX:+UseG1GC -jar myapp.jar

ZGC : Z Garbage Collector

  • Expérimental dans Java 11, stable depuis Java 15.
  • N’interrompt quasiment pas l’application : les pauses sont généralement <10 ms, même avec 1–2 To de tas.
  • Utilise des « marquages colorés » (coloring) et des pointeurs spéciaux.
  • Nécessite une JVM 64 bits ; ne fonctionne pas sur les systèmes 32 bits.
  • Pris en charge sur Linux, macOS, Windows.

Exemple d’activation :

java -XX:+UseZGC -jar myapp.jar

Shenandoah

  • Développé par Red Hat ; objectifs proches de ZGC.
  • Pauses minimales, travail très parallèle avec l’application.
  • Pris en charge sous Linux et Windows ; présent dans certaines distributions OpenJDK.
  • Utilise des techniques similaires, mais avec d’autres algorithmes internes.

Exemple d’activation :

java -XX:+UseShenandoahGC -jar myapp.jar

Comparaison visuelle

graph TD
    A[Jeune génération] -->|Minor GC| B[Vieille génération]
    B -->|Major GC| C[Pause GC]
    D[G1 : régions] --> E[Régions sélectionnées]
    F[ZGC/Shenandoah : régions] --> G[Collecte parallèle]

5. Pratique : comment connaître et changer le GC

Comment savoir quel GC est utilisé ?

  1. Logs de la JVM : Lancez l’application avec -Xlog:gc* (Java 9+) ou -verbose:gc (jusqu’à Java 8). Les logs indiqueront quel GC est utilisé et la fréquence des pauses.
  2. jcmd : Exécutez :
    jcmd <pid> VM.flags
    
    <pid> est l’identifiant du processus Java.
  3. jvisualvm : Dans la section « Monitoring », vous pouvez voir le type de GC.

Comment changer le GC de votre application ?

Ajoutez l’option nécessaire au lancement de la JVM :

G1 GC (par défaut, vous pouvez l’indiquer explicitement) :

java -XX:+UseG1GC -jar myapp.jar

ZGC :

java -XX:+UseZGC -jar myapp.jar

Shenandoah :

java -XX:+UseShenandoahGC -jar myapp.jar

Comment définir la taille du tas et les pauses ?

  • Taille maximale du tas : -Xmx2G
  • Taille minimale du tas : -Xms512M
  • Pour G1 : pause souhaitée — -XX:MaxGCPauseMillis=200

Exemple de lancement complet :

java -Xms512M -Xmx2G -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -jar myapp.jar

6. Particularités du choix du GC selon les tâches

Quand choisir G1

  • Dans la plupart des applications serveur et desktop — excellent choix par défaut.
  • Fonctionne bien avec des tas allant de plusieurs centaines de mégaoctets à plusieurs centaines de gigaoctets.
  • Équilibre performance et pauses.

Quand choisir ZGC ou Shenandoah

  • Si l’application est sensible à la latence (latency‑critical : bourses, jeux en ligne, analytique temps réel).
  • Si le tas est énorme (des centaines de gigaoctets et plus).
  • Si seules des pauses minimales (millisecondes) sont acceptables.
  • Nécessitent Java 11+ (ZGC) ou Java 12+ (Shenandoah).

Quand Parallel GC suffit

  • Pour de petites applications où le débit maximal est important et où les pauses ne sont pas critiques.
  • Pour le traitement batch, où l’on peut « supporter » un arrêt sur Full GC.

7. Exemple : comparaison du comportement des GC sur une simple application

Une petite application qui génère beaucoup d’objets temporaires (simulation de traitement de commandes) :

public class GCSimulator {
    public static void main(String[] args) {
        while (true) {
            // Nous créons 100 000 objets à chaque cycle
            for (int i = 0; i < 100_000; i++) {
                String s = new String("Order-" + i);
            }
            // On fait une courte pause
            try { Thread.sleep(100); } catch (InterruptedException e) {}
        }
    }
}

Lancez-le avec différents GC et regardez les logs :

java -Xmx256M -XX:+UseG1GC -Xlog:gc* GCSimulator
java -Xmx256M -XX:+UseZGC -Xlog:gc* GCSimulator

Que verrez-vous ?
G1 effectuera des pauses fréquentes mais courtes. ZGC/Shenandoah — des pauses encore plus courtes, mais potentiellement plus fréquentes. Parallel GC — des pauses plus longues, mais plus rares.

8. Erreurs typiques et nuances avec le GC

Erreur n° 1 : penser que le GC résoudra tous les problèmes de mémoire. Le GC n’est pas une baguette magique. Si vous gardez des références à des objets inutiles, aucun GC n’aidera — vous aurez une fuite mémoire.

Erreur n° 2 : appel forcé à System.gc(). La JVM sait mieux que vous quand collecter. Forcer le GC peut provoquer une longue pause et dégrader les performances.

Erreur n° 3 : ignorer les logs du GC. Sans consulter les logs, vous pourriez ne pas remarquer que votre application « gèle » régulièrement sur un Full GC.

Erreur n° 4 : utiliser des GC obsolètes. Par exemple, CMS n’est plus maintenu. Il vaut mieux passer à G1 ou à des collecteurs modernes à faible latence.

Erreur n° 5 : mauvais choix de GC pour la tâche. Si votre application est latency‑critical et que vous utilisez Parallel GC — attendez-vous à de longues pauses. Si vous faites du traitement batch mais activez ZGC — vous subirez des surcoûts inutiles.

Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION