Introduction : « Casser n’est pas construire »
\nChaque 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.
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.
\nDans 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\nUn peu de théorie : pourquoi Java n’est pas « éternel »
\nDe 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.
\nEn 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.
Méthode n°1 : Le classique — HashMap « oublieux »
\nL’anti-patron le plus populaire, qui passe d’article en article. Mais on ne peut pas l’ignorer.
\nComment fonctionne la fuite
\nVous 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.
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\nPourquoi 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.
Meilleure pratique 2025
\n- \n
- Utilisez
WeakHashMapsi les clés sont des objets temporaires. \n - Pour la mise en cache —
CaffeineouGuava Cacheavec des limites de taille/temps. \n - N’utilisez jamais
StringBuildercomme clé — uniquement des types immutables. \n
Méthode n°2 : Les collections statiques — le tueur silencieux
\nLes 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.
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<