BT

Diffuser les Connaissances et l'Innovation dans le Développement Logiciel d'Entreprise

Contribuez

Sujets

Sélectionner votre région

Accueil InfoQ Articles Plaidoyer pour faire de G1 le garbage collector par défaut en Java 9

Plaidoyer pour faire de G1 le garbage collector par défaut en Java 9

Retrouvez cet article dans notre eMag InfoQ FR consacré à Java 8.

 

J'ai présenté et discuté du Garbage First Garbage Collector ici sur InfoQ dans les articles suivants : G1: One Garbage Collector To Rule Them All et Tips for Tuning the Garbage First Garbage Collector.

Aujourd'hui, je voudrais parler du JEP 248, la proposition de faire de G1 le GC par défaut, pour OpenJDK 9. Avec OpenJDK 8, le GC haut débit (aussi connu sous le nom de Parallel GC), et plus récemment ParallelOld GC (ParallelOld signifie que les propriétés -XX:+UseParallelGC et -XX:+UseParallelOldGC sont toutes deux activées) sont les GC par défaut pour OpenJDK. Pour activer un GC différent, il faut l'activer explicitement sur la ligne de commande. Par exemple, si vous voulez activer le G1GC, vous devez le sélectionner sur la ligne de commande en utilisant -XX:+UseG1GC.

La proposition d'utiliser G1GC comme le GC par défaut d'OpenJDK 9 a été une grande source d'inquiétude dans la communauté, qui ont mené à des discussions incroyables, puis ont conduit à mettre à jour la proposition originale pour incorporer une clause permettant de retourner à l'utilisation du Parallel GC par défaut.

Et donc, pourquoi le G1GC ?

Vous devez être familier avec le compromis de l'optimisation logicielle : les logiciels peuvent être optimisés pour la latence, le débit ou l'empreinte. C'est aussi vrai pour les optimisations des GC et est reflété dans les différentes implémentations de GC populaires. Vous pouvez aussi vous concentrer sur deux des trois propriétés, mais essayer d'optimiser pour les trois est extrêmement complexe. Les algorithmes de GC d'OpenJDK Hotspot sont conçus pour favoriser l'une de ces caractéristiques. Par exemple, Serial GC est optimisé pour avoir une empreinte minimale, Parallel GC est optimisé pour le débit, tandis que (essentiellement) Concurrent Mark and Sweep GC (connu sous le nom de CMS) est optimisé pour réduire les latences causées par le GC et améliorer les temps de réponse. Donc, pourquoi avons nous besoin de G1 ?

G1GC arrive comme un remplaçant de long terme du CMS. CMS dans sont état actuel souffre d'un problème pathologique qui va le conduire à des échecs du mode concurrent, et ensuite à des compactions de la heap. Vous pouvez tuner CMS pour repousser la compaction complète de la heap, actuellement faite sur un seul thread, mais ça ne peut pas être évité complètement. Dans le futur, la compaction complète pourrait être améliorée pour utiliser plusieurs threads pour une exécution plus rapide, mais encore une fois, ça ne peut pas être évité.

Un autre point important est que même pour le développeur de GC expérimenté, la maintenance de CMS est difficile ; un des objectifs pour les mainteneurs des GC d'Hotspot est de maintenir CMS stable.

De plus, CMS GC, Parallel GC et G1 GC sont tous implémentés avec des frameworks différents. Le coût de maintenance de ces trois GC, chacun utilisant son propre framework est élevé. Il me semble que le framework utilisant des régions de G1GC, pour lequel l'unité de collection est la région et différentes régions peuvent constituer les différentes générations dans la heap, est la voie de l'avenir. IBM a son Balanced GC, Azul a C4 et plus récemment, il y a eu la proposition dans OpenJDK appelée Shenandoah. Il ne serait pas surprenant de voir une implémentation similaire de GC ciblant le débit basé sur des régions, qui pourrait offrir le débit et les capacités adaptatives de Parallel GC. Le nombre de frameworks de GC présents dans Hotspot pourrait être réduit, et les coûts de maintenances par la même, ce qui permettrait le développement plus rapide de nouvelles fonctionnalités pour le GC.

G1 GC est devenu supporté dans OpenJDK 7 mise à jour 4, et depuis il est devenu toujours meilleur et plus robuste avec beaucoup d'aide de la part de la communauté OpenJDK. Pour en apprendre plus sur G1, je vous recommande les articles InfoQ mentionnés plus haut, mais je vais le résumer en quelques points clés :

  • G1 GC propose un framework de heap par régions.
    • Cette caractéristique rend les générations très paramétrables, puisque l'unité de collection (la région) est plus petite que la génération. Augmenter et réduire la taille des générations est aussi simple que d'ajouter ou retirer une région. Note : même si la heap est contigüe ; les régions dans une génération n'ont pas besoin d'être contigües.
  • G1 GC est conçu sur le principe de collecte de plus de garbage possible dès le début.
    • G1 a différents ensembles collectables (CSet) pour les collectes jeunes (young) et mixtes (pour plus d'informations, se référer à cet article). Pour les collectes mixtes, le CSet est composé de toutes les régions jeunes et de quelques régions candidates de la vieille génération. Le cycle de marquage concurrent aide à identifier ces régions candidates, et elles sont ajoutées à l'ensemble de régions à collecter. Les leviers de configuration disponibles pour la vieille génération dans G1 GC sont plus directs, plus nombreux et proposent plus de contrôle que ceux sur la limitation de la taille de la heap proposés par Parallel GC, ou ceux qui limitent la taille et le taux d'occupation de la heap qui déclenche le marquage dans CMS. Dans le future tel que je l'imagine, il y a un G1 GC adaptatif qui peut faire des prédictions pour optimiser l'ensemble de régions à collecter et le taux de marquage basé sur des statistiques collectées durant les cycles de marquage et de collection.
    • Un échec d'évacuation dans le G1 GC est aussi quelque chose de configurable. A la différence de CMS, la fragmentation dans G1 n'est pas quelque chose qui s'accumule au fil du temps et qui finit par déclencher des collectes très coûteuses. Pour G1, la fragmentation est minime et gérée par des paramètres. De la fragmentation est introduite par les très gros objets qui ne suivent pas le chemin d'allocation normal. Ces objets très gros (connus sous le nom d'objet humongous) sont alloués directement dans la vieille génération dans les régions humongous. (Note : pour en apprendre plus sur les objets humongous et les allocations humongous, se référer à cet article). Mais quand ces objets humongous meurent, ils sont collectés et la fragmentation disparait avec eux. Dans l'état actuel, ça peut toujours être un cauchemar de tuning de l'occupation de la heap et des régions, surtout quand les ressources sont limitées ; mais encore une fois, rendre l'algorithme de G1 plus adaptatif permet aux utilisateurs finaux d'être exposés à moins de pauses.
  • G1 GC est scalable !
    • L'algorithme de G1 a été conçu avec la scalabilité en tête. Comparé au ParallelGC, vous avez quelque chose qui scale avec la taille de la heap et la charge sans trop de compromis sur le débit de l'application.

Pourquoi maintenant ?

La proposition cible OpenJDK 9. La disponibilité générale d'OpenJDK 9 est prévue pour 2017. L'espoir est que les membres de la communauté OpenJDK qui choisissent de travailler avec les versions de tests seront ceux qui vont tester la possibilité d'utiliser G1 GC comme le GC par défaut et vont aussi aider en fournissant du feedback et proposer des changements dans le code. Aussi, les seuls utilisateurs finaux qui seront impactés sont ceux qui ne configurent pas aujourd'hui explicitement le GC. Ceux qui le font ne seront pas impactés par ce changement. Ceux qui ne le font pas auront G1 GC au lien de Parallel GC, et s'ils veulent continuer à utiliser Parallel GC, il suffit d'ajouter-XX:+UseParallelGC (le paramètre actuel par défaut qui active la collecte parallèle de la jeune génération) sur les paramètres de la JVM. Note : avec l'introduction de -XX:+UseParallelOldGC dans le JDK 5 mise à jour 6, pour tous les build récents, si vous ajouter le flag -XX:+UseParallelGC, -XX:+UseParallelOldGC sera aussi activé, avec donc des threads parallèles aussi utilisés pour les collectes complètes. Si vous utilisez des builds au moins aussi récents que le JDK 6, utiliser ces flags vous donne le même comportement qu'avant.

Quand choisiriez-vous le G1 GC plutôt que Parallel GC ?

Comme mentionné dans cet article, Parallel GC ne fait pas de collecte incrémentale, il finit donc par sacrifier la latence pour le débit. Pour les grandes heap, quand la charge augmente, les temps de pauses GC augmentent aussi, pouvant compromettre les SLAs liés à la latence. G1 peut vous aider à respecter votre SLA avec une heap plus petite puisque les pauses des collectes mixtes de G1 doivent être considérablement plus courtes que les collectes complètes de Parallel GC.

Quand choisiriez-vous le G1 GC plutôt que CMS ?

Dans son état actuel, un G1 tuné peut et va atteindre les objectifs de latence que CMS ne peut pas respecter à cause des problèmes de fragmentation et d'échecs du mode concurrent. Les temps de pauses dans le pire des cas avec les collections mixtes sont attendus comme étant plus courts que les temps de pauses dans le pire des cas avec CMS. Comme mentionné plus tôt, on ne peut que repousser et pas éviter complètement les pauses de CMS. Certains développeurs utilisant CMS ont imaginé des contournements pour combattre la fragmentation en allouant des objets de tailles similaires. Mais ces contournements ont été développés autour de CMS. La nature même de CMS fait qu'il souffre de fragmentation et va finir par nécessiter une compaction. Je sais que certaines sociétés comme Google construisent leur propre version de JDK basée sur OpenJDK avec des changements adaptés à leurs besoins. Par exemple, dans une tentative de réduire la fragmentation, un ingénieur de Google a mentionné le fait qu'ils ont ajouté une forme de compaction incrémentale à leur version (privée) de CMS, lors de la phase de remarquage, et ils ont aussi rendu leur CMS plus stable (voir ici).

Note : la compaction incrémentale vient avec ses propres coûts. Google l'a probablement ajouté après avoir pesé le pour et le contre dans leurs cas d'utilisation spécifiques.

Pourquoi ce JEP est-il devenu un sujet aussi brûlant ?

De nombreux membres de la communauté OpenJDK ont exprimé leurs inquiétudes sur le fait que G1 soit prêt pour être mis en avant. Des membres ont donné leurs observations sur leur expérience avec G1. Depuis que G1 est supporté, il a été promu comme le remplaçant de CMS. Mais la communauté a l'impression que G1 est maintenant le remplaçant de Parallel GC (le GC par défaut) et pas de CMS. Donc il est largement pensé qu'alors qu'il devrait y avoir des données comparant CMS et G1 (en raison des entreprises migrant de CMS à G1), il n'y a pas suffisamment de données comparant G1 à Parallel GC. De plus, les données d'utilisation semblent indiquer que la majorité des entreprises utilisent le GC par défaut, et qu'il y aura donc un changement de comportement observable quand G1 deviendra le GC par défaut. Il y a aussi des observations sur le fait que G1 présente des problèmes importants (mais très difficiles à reproduire) de corruption d'index et de tels problèmes doivent être étudiés et rectifiés avant que G1 ne soit activé par défaut. Certains demandent aussi s'il y a encore besoin d'un seul GC par défaut qui ne soit pas basé sur les ergonomics. (Par exemple, depuis Java 5, si votre système est identifié comme étant de type serveur, la JVM sera de type serveur plutôt que cliente (ref: http://docs.oracle.com/javase/7/docs/technotes/guides/vm/server-class.html).

Conclusion

Après de nombreux échanges, Charlie Hunt, Performance Architect pour Oracle à résumé et proposé le plan suivant. (Note : l'extrait suivant provient d'ici) :

  • Faire de G1 le GC par défaut dans le JDK 9, continuer à évaluer G1 et l'améliorer dans le JDK 9.
  • Limiter les risques en repassant à Parallel GC avant la publication de la version finale de JDK 9, si justifié d'après les observations faites sur les préversions de JDK 9 et les dernières versions du JDK 8.
  • Résoudre le problème de la sélection du GC par défaut selon des ergonomics si les observations futures montrent que c'est nécessaire. Aussi, Staffan Friberg de l'équipe Java SE Performance chez Oracle a exhorté la communauté à collecter des mesures pour les métriques clés. Je l'ai paraphrasé pour plus de concision :
  • Le temps de démarrage : pour s'assurer que la complexité de l'infrastructure de G1 n'introduise pas de délai à l'initialisation de la JVM.
  • Le débit : G1 sera confronté au GC favorisant le débit. G1 possède aussi des barrières avant et après la lecture de référence en mémoire. La métrique du débit est la clé pour comprendre quelle charge supplémentaire est imposée par ces barrières sur les applications.
  • L'empreinte : G1 a des remembered sets et collection sets qui augmentent l'empreinte. Les données collectées sur des applications devraient fournir suffisamment d'informations pour comprendre l'impact de l'augmentation de l'empreinte.
  • La performance out of the box : les entreprises qui utilisent le GC par défaut utilisent souvent aussi les paramètres par défaut. Il est donc important de comprendre la performance out of the box de G1. Ici, les ergonomics et l'adaptabilité jouent un rôle important. Staffan a aussi aidé à identifier le fait que les applications utilisant le GC par défaut seront celles impactées par le changement de GC par défaut. De façon similaire, les scripts qui ne précisent pas le GC ou les interfaces qui ne précisent que la taille de la heap et la taille des générations sur la ligne de commande seront impactés par le changement du GC par défaut.

Remerciements

Je voudrais exprimer ma gratitude à Charlie Hunt pour la relecture de cet article.

A Propos de l'Auteure

Monica Beckwith est Consultante en Performance Java. Ses expériences passées incluent la collaboration avec Oracle / Sun et AMD et l'optimisation de la JVM pour les systèmes de classe serveur. Monica a été élue JavaOne Rock Star 2013 et a été la Performance Lead pour les Ramasse-Miettes Garbage First (G1 GC). Vous pouvez suivre Monica sur Twitter mon_beck.

Evaluer cet article

Pertinence
Style

Contenu Éducatif

BT