Skip to content

Latest commit

 

History

History
133 lines (82 loc) · 10.5 KB

README.md

File metadata and controls

133 lines (82 loc) · 10.5 KB

🔝 Retour au Sommaire

14. Performance et gestion de la mémoire 🌳

Niveau : confirmé · Indicateur : 🌳 Architecture & performance
Prérequis techniques : JDK 25 LTS · Eclipse IDE 2026-06 · notions de concurrence (chapitre 8)

La performance est sans doute le domaine où l'écart entre l'intuition et la réalité est le plus grand. On « sait » qu'une boucle est lente, on « pense » qu'un new coûte cher, on « croit » qu'un thread de plus accélérera tout… et la mesure dit presque toujours autre chose. Ce chapitre repose donc sur une règle non négociable, qui sera répétée à chaque section : on mesure, on ne devine pas.

L'autre piège est plus insidieux. La JVM moderne fait énormément de travail pour vous — compilation à la volée, optimisations adaptatives, ramasse-miettes concurrent. Une grande partie de ce que les développeurs appellent « optimiser » revient en réalité à se battre contre la JVM au lieu de la laisser faire. Comprendre ce que la machine fait déjà est la première étape pour écrire du code rapide ; c'est aussi ce qui permet de savoir quand s'arrêter.

« Premature optimization is the root of all evil » — Donald Knuth

La formule est célèbre, et presque toujours citée tronquée. Knuth ajoutait : « Yet we should not pass up our opportunities in that critical 3 % ». Tout l'art de la performance tient dans cet équilibre : ne pas complexifier à l'aveugle les 97 % du code qui n'en ont aucun besoin, mais identifier puis traiter rigoureusement les 3 % qui comptent vraiment. Optimiser sans mesure, c'est optimiser le mauvais 97 %.


🎯 Objectifs du chapitre

À l'issue de ce chapitre, vous serez capable de :

  • Décrire le modèle mémoire de la JVM — pile, tas, metaspace — et savoir où « vit » chaque donnée d'un programme Java.
  • Comprendre le rôle du Garbage Collector, distinguer les algorithmes disponibles (G1, ZGC, Parallel, Serial) et choisir le bon selon un objectif de débit ou de latence.
  • Diagnostiquer une fuite mémoire et analyser un heap dump avec Eclipse Memory Analyzer (MAT).
  • Expliquer ce que fait le compilateur JIT (C1/C2, compilation étagée, optimisations guidées par le profil) et pourquoi une simple montée de version LTS améliore souvent les performances « gratuitement ».
  • Écrire un benchmark fiable avec JMH, en évitant les erreurs classiques de la micro-mesure « maison ».
  • Appliquer les bonnes pratiques de performance réellement utiles en Java, et reconnaître les fausses bonnes idées.
  • Profiler une application depuis Eclipse à l'aide de VisualVM, async-profiler et Java Flight Recorder.

À noter : ce chapitre vise la compréhension et le diagnostic, pas l'optimisation prématurée. L'objectif n'est pas de vous transformer en chasseur de microsecondes, mais de vous donner la méthode et les outils pour intervenir avec discernement, là où c'est justifié.


🧭 Vue d'ensemble du chapitre

Le chapitre suit une progression logique : comprendre comment la JVM gère la mémoire et exécute le code, puis mesurer, puis agir.

§ Section En bref Indicateur
14.1 Modèle mémoire de la JVM Pile, tas, metaspace : où vivent les données et pourquoi
14.2 Garbage Collection G1, ZGC, Parallel : principes, choix, tuning de base, lecture des journaux GC
14.3 Allocations & fuites mémoire Repérer une fuite, analyser un heap dump avec Eclipse MAT 🔧
14.4 Le compilateur JIT C1/C2, compilation étagée, PGO ; les gains offerts par les LTS récentes 🆕
14.5 Benchmarking avec JMH Mesurer juste : warmup, dead code elimination, pièges de la micro-mesure
14.6 Bonnes pratiques de performance Ce qui aide vraiment — et les mythes à oublier
14.7 Profilage depuis Eclipse VisualVM, async-profiler, JFR : trouver le vrai goulot d'étranglement 🔧

Le fil conducteur

Trois questions structurent l'ensemble du chapitre :

  1. Où va la mémoire ? → modèle mémoire (14.1), allocations et fuites (14.3).
  2. Où va le temps ? → JIT (14.4), profilage (14.7), GC (14.2).
  3. Comment le sait-on, sans se tromper ? → benchmarking (14.5), méthode de mesure transverse à tout le chapitre.

Tant qu'on ne sait pas répondre à la troisième question, on ne devrait pas toucher aux deux premières.


🧠 La bonne posture face à la performance

Avant d'entrer dans la technique, quelques principes de méthode qui valent pour tout le chapitre.

1. Mesurer avant, mesurer après. Une optimisation non mesurée n'est pas une optimisation : c'est un pari. On établit une baseline, on change une chose, on remesure. Sans cette discipline, on accumule de la complexité sans preuve de gain.

2. Optimiser le goulot d'étranglement, rien d'autre. Améliorer de 50 % une fonction qui représente 1 % du temps d'exécution ne fait gagner que 0,5 %. Le profilage (14.7) sert précisément à identifier où se trouve réellement le coût — qui est presque toujours ailleurs que là où on l'imaginait.

3. La lisibilité d'abord, la micro-optimisation ensuite. Un code clair est plus facile à corriger, à faire évoluer… et à optimiser quand c'est nécessaire. Sacrifier la lisibilité pour un gain hypothétique est presque toujours un mauvais échange. Le code « malin » est le cauchemar de la maintenance.

4. La JVM est votre alliée. Le JIT compile et ré-optimise le code à chaud, le GC récupère la mémoire de façon concurrente, l'escape analysis élimine des allocations. Beaucoup de « tricks » d'optimisation hérités de l'ancien temps sont aujourd'hui inutiles, voire contre-productifs : ils empêchent la JVM de faire mieux que vous.

5. La performance n'est pas qu'une question de vitesse. Latence, débit, empreinte mémoire, temps de démarrage, prévisibilité (pauses GC) : ce sont des objectifs différents, parfois contradictoires. On n'optimise pas une API web et un traitement par lots de la même manière. Toujours commencer par : « quel objectif, pour quel usage ? »


🔧 L'angle Eclipse

Ce chapitre exploite réellement l'outillage de l'IDE et de la plateforme :

  • Eclipse Memory Analyzer (MAT) pour ouvrir et explorer un heap dump, repérer les objets qui retiennent la mémoire et remonter aux GC roots (§14.3).
  • Intégration des profileurs depuis Eclipse — VisualVM, async-profiler — et exploitation des enregistrements Java Flight Recorder (JFR) (§14.7).
  • Lecture et interprétation des journaux GC (unified logging, -Xlog:gc*) directement dans la console (§14.2).

💡 Le profilage et l'analyse de mémoire sont souvent perçus comme réservés aux experts. C'est faux : avec MAT et JFR, l'essentiel est accessible dès qu'on connaît la méthode. Ce chapitre vise précisément à démystifier ces outils.


🆕 Ce que JDK 25 LTS change pour la performance

Travailler sur une LTS récente est en soi un levier de performance. Sans rien changer à votre code, une montée de version apporte régulièrement :

  • des ramasse-miettes plus efficaces — G1 affiné au fil des versions, ZGC désormais générationnel, conçu pour des pauses de l'ordre de la milliseconde sur de très gros tas ;
  • un JIT amélioré : meilleures heuristiques de compilation, optimisations supplémentaires, escape analysis plus poussée ;
  • les avancées du Project Leyden sur le temps de démarrage et le coût de mise en chauffe (chargement et liaison de classes anticipés), particulièrement utiles pour les applications à durée de vie courte et les déploiements cloud ;
  • un socle qui prépare les chantiers de fond — Valhalla (types valeur, qui réduiront à terme la pression mémoire) et Panama (accès mémoire et code natif performants via l'API Foreign Function & Memory).

Idée à retenir : la stratégie de performance la moins coûteuse est souvent la plus simple — rester sur une LTS à jour. Avant d'investir dans des optimisations manuelles risquées, vérifiez ce que la plateforme vous offre déjà gratuitement. (Voir aussi le chapitre 18 sur la feuille de route et la stratégie de versions.)


📋 Prérequis

Pour tirer pleinement parti de ce chapitre, il est recommandé d'avoir assimilé :

  • les fondamentaux du langage et la POO (parties 1, chapitres 2–3) ;
  • le framework des collections et les Streams (chapitre 4) — la performance des collections y est centrale ;
  • la concurrence (chapitre 8) — le parallélisme et le thread-safety conditionnent fortement les performances réelles ;
  • des notions de build (chapitre 6), car JMH et certains outils s'intègrent via Maven/Gradle.

Une familiarité avec la ligne de commande et les drapeaux JVM (-Xmx, -Xlog, etc.) est un plus, mais les bases nécessaires sont rappelées au fil des sections.


⚠️ Points de vigilance

  • Ne pas optimiser sans profil. C'est le message principal du chapitre, répété à dessein. Une optimisation guidée par l'intuition seule a de fortes chances de viser le mauvais endroit.
  • Se méfier des micro-benchmarks « maison ». Sans warmup ni précautions contre les optimisations du JIT, ils donnent des chiffres faux — souvent spectaculairement faux. D'où l'existence de JMH (§14.5).
  • Le tuning GC est un dernier recours. Avant de toucher aux drapeaux du ramasse-miettes, on corrige les allocations excessives et les fuites. Un tuning mal compris dégrade plus souvent qu'il n'améliore.
  • La performance se mesure en conditions réalistes. Un gain observé sur un poste de développement ne se transpose pas mécaniquement en production (matériel, charge, données réelles, JVM partagée). Profiler là où ça compte.

▶️ Pour commencer

On démarre par les fondations : où la JVM range-t-elle les données, et selon quelles règles ? Comprendre la distinction entre la pile, le tas et le metaspace est le socle de tout le reste — c'est ce qui rend intelligibles le GC, les fuites mémoire et le profilage qui suivront.

➡️ 14.1 — Modèle mémoire de la JVM (pile, tas, metaspace)


Chapitre 14 — Performance et gestion de la mémoire · Formation Java avec Eclipse IDE · JDK 25 LTS · Eclipse 2026-06

⏭ Modèle mémoire de la JVM (pile, tas, metaspace)