Why Java Code Leaks? Creating and Catching Memory Leaks in 2025

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: "Breaking is Easier Than Building"

\n

Every Java developer has cursed a memory leak in their production application at least once. The application crashes with OutOfMemoryError, the server reboots, and users write angry emails. But there is another side to the coin: how to create a memory leak in Java intentionally? It sounds crazy, but this very question (original from Stack Overflow) has gathered thousands of views.

\n

Why should you know this? To understand the mechanics of the garbage collector (GC) and the antipatterns that turn your code into a sieve. In 2025, when Java 21 with virtual threads and Loom is already the standard, old leaks haven't gone away — they just disguise themselves as new features.

\n

In this article, we will not only show how to "break" a Java application, but also provide best practices for detecting leaks in 2025. Ready? Let's go!

\n\n

A Bit of Theory: Why Java Isn't "Immortal"

\n

Many beginners firmly believe that the garbage collector (GC) automatically removes everything that is not needed. Reality is harsh: a memory leak in Java is possible if an object remains reachable from root references (GC roots) but is no longer needed by the application.

\n

In 2025, with the advent of Value Objects (Project Valhalla) and improved ZGC (a collector with pause time < 1 ms), the performance situation has improved, but the logic of leaks remains the same. The GC is not a telepath — it doesn't know that you forgot to nullify a reference in a HashMap.

\n\n

Method #1: The Classic — "Forgetful" HashMap

\n

The most popular antipattern, which migrates from article to article. But we can't do without it.

\n

How the Leak Works

\n

You put objects into a HashMap (or HashSet) as keys, but forget to remove them. If the keys are mutable objects that change their hash code after insertion, you are guaranteed to get a Java memory leak.

\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            // "Forgot" to remove — keys hang forever\n        }\n        System.out.println("Map size: " + map.size());\n        // Leak: 100_000 StringBuilder + String objects in the heap\n    }\n}\n
\n\n

Why is this a leak? As long as the reference to map is alive, all keys and values remain in the heap. Even if the application logic no longer uses this data, the GC will not touch it.

\n\n

Best Practice 2025

\n
    \n
  • Use WeakHashMap if the keys are temporary objects.
  • \n
  • For caching — use Caffeine or Guava Cache with size/time limits.
  • \n
  • Never use StringBuilder as a key — only immutable types.
  • \n
\n\n

Method #2: Static Collections — The Silent Killer

\n

Static fields live until the JVM shuts down. If you put data into a static List or static Map and don't clean it up, you get a Java memory leak at the class level.

\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        // Each call adds 10 MB to the static list\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 is inevitable\n    }\n}\n
\n\n

<

Blogs

Book Recommendations