Pourquoi le code Java fuit-il ? Créons et attrapons les fuites mémoire en 2025

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Introduction : « Casser n’est pas construire »

\n

Chaque développeur Java a au moins une fois maudit une fuite mémoire (memory leak) dans son application en production. L’application plante avec OutOfMemoryError, le serveur redémarre, les utilisateurs envoient des emails furieux. Mais il y a un revers à la médaille : comment créer une fuite mémoire en Java intentionnellement ? Cela semble fou, mais c’est précisément cette question (originale de Stack Overflow) qui a rassemblé des milliers de vues.

\n

Pourquoi savoir cela ? Pour comprendre la mécanique du ramasse-miettes (GC) et les anti-patrons qui transforment votre code en passoire. En 2025, alors que Java 21 avec les threads virtuels et Loom est déjà la norme, les anciennes fuites n’ont pas disparu — elles se cachent simplement sous de nouvelles fonctionnalités.

\n

Dans cet article, nous ne montrerons pas seulement comment « gâcher » une application Java, mais nous donnerons aussi les meilleures pratiques de détection des fuites en 2025. Prêts ? C’est parti !

\n\n

Un peu de théorie : pourquoi Java n’est pas « éternel »

\n

De nombreux débutants croient fermement que le ramasse-miettes (GC) supprime automatiquement tout ce qui n’est pas nécessaire. La réalité est cruelle : une fuite mémoire en Java est possible si un objet reste atteignable (reachable) à partir des racines GC (GC roots), mais n’est plus nécessaire à l’application.

\n

En 2025, avec l’arrivée des Value Objects (Project Valhalla) et des ZGC améliorés (ramasse-miettes avec un temps de pause < 1 ms), la situation des performances s’est améliorée, mais la logique des fuites reste la même. Le GC n’est pas télépathe — il ne sait pas que vous avez oublié de mettre une référence à null dans un HashMap.

\n\n

Méthode n°1 : Le classique — HashMap « oublieux »

\n

L’anti-patron le plus populaire, qui passe d’article en article. Mais on ne peut pas l’ignorer.

\n

Comment fonctionne la fuite

\n

Vous placez des objets dans un HashMap (ou HashSet) comme clés, mais vous oubliez de les supprimer. Si les clés sont des objets mutables qui changent de code de hachage après l’insertion, vous obtiendrez à coup sûr une fuite mémoire Java.

\n\n
import java.util.HashMap;\nimport java.util.Map;\n\npublic class LeakyMapExample {\n    public static void main(String[] args) {\n        Map<StringBuilder, String> map = new HashMap<>();\n\n        for (int i = 0; i < 100_000; i++) {\n            StringBuilder key = new StringBuilder("key_" + i);\n            map.put(key, "value_" + i);\n            // "Oubli" de supprimer — les clés restent pour toujours\n        }\n        System.out.println("Taille de la carte : " + map.size());\n        // Fuite : 100_000 objets StringBuilder + String dans le tas\n    }\n}\n
\n\n

Pourquoi est-ce une fuite ? Tant que la référence à map est vivante, toutes les clés et valeurs restent dans le heap. Même si la logique de l’application n’utilise plus ces données, le GC ne les touchera pas.

\n\n

Meilleure pratique 2025

\n
    \n
  • Utilisez WeakHashMap si les clés sont des objets temporaires.
  • \n
  • Pour la mise en cache — Caffeine ou Guava Cache avec des limites de taille/temps.
  • \n
  • N’utilisez jamais StringBuilder comme clé — uniquement des types immutables.
  • \n
\n\n

Méthode n°2 : Les collections statiques — le tueur silencieux

\n

Les champs statiques vivent jusqu’à la fin de la JVM. Si vous stockez des données dans une static List ou un static Map et ne les nettoyez pas, vous obtenez une fuite mémoire Java au niveau de la classe.

\n\n
import java.util.ArrayList;\nimport java.util.List;\n\npublic class StaticLeak {\n    private static final List<byte[]> CACHE = new ArrayList<>();\n\n    public static void addData() {\n        // Chaque appel ajoute 10 Mo à la liste statique\n        CACHE.add(new byte[10 * 1024 * 1024]);\n    }\n\n    public static void main(String[] args) throws Exception {\n        for (int i = 0; i < 1_000; i++) {\n            addData();\n            Thread.sleep(100);\n        }\n        // OutOfMemoryError inévitable\n    }\n}\n
\n\n

<

Blogs

Book Recommendations