Einleitung: „Kaputtmachen ist leichter als Bauen“
\nJeder Java-Entwickler hat schon einmal ein Speicherleck (memory leak) in seiner Produktionsanwendung verflucht. Die Anwendung stürzt mit OutOfMemoryError ab, der Server startet neu, Benutzer schreiben wütende E-Mails. Aber es gibt auch eine andere Seite der Medaille: Wie erstellt man absichtlich ein Speicherleck in Java? Klingt verrückt, aber genau diese Frage (Original von Stack Overflow) hat tausende von Aufrufen gesammelt.
Warum sollte man das wissen? Um die Mechanik des Garbage Collectors (GC) und die Antipatterns zu verstehen, die Ihren Code löchrig wie ein Sieb machen. Im Jahr 2025, wenn Java 21 mit virtuellen Threads und Loom bereits Standard ist, sind die alten Lecks nicht verschwunden – sie tarnen sich nur unter neuen Funktionen.
\nIn diesem Artikel zeigen wir nicht nur, wie man eine Java-Anwendung „ruiniert“, sondern geben auch Best Practices zur Erkennung von Lecks im Jahr 2025. Bereit? Los geht's!
\n\nEin wenig Theorie: Warum Java nicht „ewig“ ist
\nViele Anfänger glauben fest daran, dass der Garbage Collector (GC) automatisch alles löscht, was nicht benötigt wird. Die Realität ist grausam: Ein Speicherleck in Java ist möglich, wenn ein Objekt von den GC-Roots aus erreichbar (reachable) bleibt, aber von der Anwendung nicht mehr benötigt wird.
\nIm Jahr 2025, mit dem Aufkommen von Value Objects (Project Valhalla) und verbesserten ZGC (Garbage Collector mit Pausenzeiten < 1 ms), hat sich die Leistung verbessert, aber die Logik der Lecks ist dieselbe geblieben. Der GC ist kein Gedankenleser – er weiß nicht, dass Sie vergessen haben, eine Referenz in einer HashMap zu löschen.
Methode Nr. 1: Klassiker – die „vergessliche“ HashMap
\nDas beliebteste Antipattern, das von Artikel zu Artikel wandert. Aber ohne ihn geht es nicht.
\nWie das Leck funktioniert
\nSie legen Objekte als Schlüssel in eine HashMap (oder HashSet), vergessen aber, sie zu entfernen. Wenn die Schlüssel mutable-Objekte sind, die nach dem Einfügen ihren Hash-Code ändern, haben Sie garantiert ein Speicherleck in 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 // "Vergessen" zu löschen – Schlüssel hängen für immer\n }\n System.out.println("Kartengröße: " + map.size());\n // Leck: 100_000 StringBuilder-Objekte + String im Heap\n }\n}\n\n\nWarum ist das ein Leck? Solange die Referenz auf map lebt, bleiben alle Schlüssel und Werte im Heap. Selbst wenn die Anwendungslogik diese Daten nicht mehr verwendet, rührt der GC sie nicht an.
Best Practice 2025
\n- \n
- Verwenden Sie
WeakHashMap, wenn die Schlüssel temporäre Objekte sind. \n - Für Caching –
CaffeineoderGuava Cachemit Größen-/Zeitlimits. \n - Verwenden Sie niemals
StringBuilderals Schlüssel – nur immutable-Typen. \n
Methode Nr. 2: Statische Kollektionen – der stille Killer
\nStatische Felder leben bis zum Ende der JVM. Wenn Sie Daten in eine static List oder static Map legen und sie nicht bereinigen, erhalten Sie ein Speicherleck in Java auf Klassenebene.
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 // Jeder Aufruf fügt 10 MB zur statischen Liste hinzu\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 ist unvermeidlich\n }\n}\n\n\n<