CodeGym /Courses /JAVA 25 SELF /Dissecting common memory management mistakes

Dissecting common memory management mistakes

JAVA 25 SELF
Level 64, Lesson 4
Available

1. Typical mistakes when working with memory

It’s time to look at the flip side of the magic of automatic memory management. Even if you don’t write in C, where you personally track every byte, in Java you can mess things up so badly that the app will gobble memory like a hungry cat devours sausage. Let’s go through the most common mistakes and how to avoid them.

Forgotten listeners

Java often uses the “listener” pattern — an object that subscribes to another object’s events. For example, you created a button and added a click handler:

button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        // handle click
    }
});

The problem: if you forgot to remove this listener (removeActionListener) when the button or window is no longer needed, the listener stays hanging around in memory. Even if you closed the window and nulled out all references to it, the listener object still holds a reference to the window (or vice versa), preventing the garbage collector from freeing the memory.

Analogy: Imagine you moved but forgot to unsubscribe from a pizzeria’s mailing list — they keep sending ads to your old address.

Static collections that never get cleared

Static fields live as long as the class (and sometimes until the end of the app’s life). If you have a static collection:

public class Cache {
    public static final List<String> globalList = new ArrayList<>();
}

and you add objects there but never remove them, they will stay in memory forever. Even if there are no other references to those objects, the reference from the static collection won’t let the GC reclaim them.

Real example: A photo cache in a desktop app that never gets cleaned. After a couple of hours — OutOfMemoryError.

Not releasing resources (files, streams, connections)

Although Java frees memory, it doesn’t automatically close file descriptors, network connections, and other external resources. If you forget to close a file or stream, the resource will linger, and at some point the system will say: “That’s it, no more files for you!” (IOException: Too many open files).

Tip: Always use try-with-resources:

try (FileInputStream in = new FileInputStream("data.txt")) {
    // read the file
} // in.close() will be called automatically!

Large objects lingering in memory

Sometimes you create a large array or collection, use it, and then “forget to let go.” For example:

List<byte[]> bigList = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
    bigList.add(new byte[1024 * 1024]); // 1 MB each
}
// ... forgot to clear bigList

If this collection lives in a static field or in an object that isn’t removed for a long time, all that memory will remain occupied.

Inner and anonymous classes: capturing outer references

Anonymous (and inner) classes in Java keep an implicit reference to the outer object:

public class Outer {
    void doSomething() {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello from inner!");
            }
        };
        // r is stored somewhere
    }
}

If the object r ends up in a static collection or cache, it will “hold” a reference to the Outer instance even if it’s no longer needed. The result is a memory leak. With lambda expressions the situation is slightly better, but if a lambda uses fields of the outer class, the reference is still kept.

2. Mistakes when working with the garbage collector

Forcing System.gc()

Many beginners think: “Memory is running out — I’ll call System.gc() and everything will be solved!” In reality it’s only a request to the JVM, not a guarantee of immediate collection. Frequent use can dramatically hurt performance, causing long pauses and freezes. In real applications it’s better to trust the JVM — it will decide when to collect. By the way, some JVMs can ignore explicit GC calls (for example with -XX:+DisableExplicitGC).

Ignoring GC logs

GC logs show when collections happen, how long they take, and how much memory is freed. If you don’t look at these logs, you can miss warning signs: long pauses, frequent Full GC, memory leaks.

How to enable GC logs:

java -Xlog:gc* -jar MyApp.jar

or for older JVMs:

java -XX:+PrintGCDetails -XX:+PrintGCDateStamps -jar MyApp.jar

Choosing the wrong GC for the job

The choice of collector affects latency and stability. For low latency (exchanges, online games) a parallel stop-the-world Parallel GC is a bad idea: it can “freeze” all threads during collection. Consider G1 GC, ZGC, or Shenandoah.

3. Mistakes with collections

Using HashMap instead of WeakHashMap for caches.
If you’re building a cache where objects should be automatically removed when there are no more “live” references to them, use WeakHashMap:

Map<Key, Value> cache = new WeakHashMap<>();

With a regular HashMap, objects will live until the cache is cleared manually, which leads to a memory leak.

Forgotten remove() for elements.
If you add objects to collections (for example, listener lists) but don’t remove them when they’re no longer needed, these objects will live forever, especially in long-lived collections (for example, static ones).

4. Best practices: how to avoid problems

Always remove listeners.
If an object subscribed to events, make sure to unsubscribe it when it’s no longer needed. It’s convenient to do this in the dispose() method or when closing a window/screen.

button.removeActionListener(myListener);

Use weak references for caches.
If a cache can do without a guarantee to retain an object, use WeakReference or collections built on them (WeakHashMap). Then the GC can free memory when needed.

Monitor memory in production.
Use jvisualvm, jconsole, or APM systems. This helps catch leaks before users complain.

Analyze a heap dump if you suspect a leak

If the application starts consuming more memory than usual, take a heap dump (for example via jmap or jvisualvm) and see which objects take up the most space. Often the culprit is found in a couple of minutes.

Tune JVM parameters.

  • -Xmx — maximum heap size
  • -Xms — initial heap size

Sensible limits help avoid OutOfMemoryError and speed up diagnosis.

5. Practice: a code example with a memory leak and its fix

Example 1: Leak via a static collection

public class MemoryLeakDemo {
    // Static collection — lives forever
    private static final List<byte[]> leakyList = new ArrayList<>();

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            leakyList.add(new byte[1024 * 1024]); // 1 MB each time
            System.out.println("Added " + (i + 1) + " MB");
        }
        // OutOfMemoryError!
    }
}

Fix: Use a local variable or clear the collection when it’s no longer needed.

public class MemoryLeakFixed {
    public static void main(String[] args) {
        List<byte[]> tempList = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            tempList.add(new byte[1024 * 1024]);
            System.out.println("Added " + (i + 1) + " MB");
        }
        // tempList = null; // You can explicitly null it out
        // Now the objects are eligible for GC after the method returns
    }
}

Example 2: Leak via a listener

public class Window {
    private final List<EventListener> listeners = new ArrayList<>();

    public void addListener(EventListener l) {
        listeners.add(l);
    }
    // No removeListener method!
}

Fix: Add a method to remove a listener and call it when closing the window.

public void removeListener(EventListener l) {
    listeners.remove(l);
}

Example 3: Cache with HashMap instead of WeakHashMap

Map<Object, Object> cache = new HashMap<>();
// ... add objects

Fix: Switch to WeakHashMap:

Map<Object, Object> cache = new WeakHashMap<>();

Tips for configuring the JVM to monitor memory

  • Enable GC logs: -Xlog:gc* or -XX:+PrintGCDetails
  • Limit the maximum heap size: -Xmx512m
  • If you use caches, watch their size and use weak references where appropriate
  • Experiment with GC: -XX:+UseG1GC, -XX:+UseZGC, -XX:+UseShenandoahGC

7. Typical mistakes when working with memory

Mistake No. 1: Forgotten listeners and subscriptions. If you added a listener to an object but forgot to remove it, the listener object (and everything it references) will stay in memory. A classic in GUIs and event-driven systems. Use removeListener/removeActionListener.

Mistake No. 2: Static collections without cleanup. Static fields live the longest. If you put objects there and don’t clean the collection, those objects will remain in memory forever. Especially insidious for unbounded caches.

Mistake No. 3: Not releasing external resources. Left a stream, file, or connection open? You waste memory and hit OS limits. Use try-with-resources and close resources.

Mistake No. 4: Forcing System.gc(). This is not a silver bullet, just a request to the JVM. It often leads to pauses and performance degradation.

Mistake No. 5: Using regular collections for caches. If objects in a cache should be removed automatically, use weak/soft references (WeakHashMap, SoftReference). Otherwise you’ll get a leak.

Mistake No. 6: Inner and anonymous classes capturing outer references. Inner classes and lambdas can implicitly hold a reference to the outer object. If they’re saved in a long-lived collection, that’s a leak.

Mistake No. 7: Ignoring GC logs. If you don’t look at GC logs, you won’t know about long pauses or frequent Full GCs — but users will, through lags and freezes. Enable -Xlog:gc* or -XX:+PrintGCDetails.

1
Task
JAVA 25 SELF, level 64, lesson 4
Locked
Not Releasing External Resources – Open Book in the Library 📖
Not Releasing External Resources – Open Book in the Library 📖
1
Task
JAVA 25 SELF, level 64, lesson 4
Locked
Cache bug with HashMap – The Museum That Never Discards Exhibits 🖼️
Cache bug with HashMap – The Museum That Never Discards Exhibits 🖼️
1
Survey/quiz
Memory and Garbage Collection, level 64, lesson 4
Unavailable
Memory and Garbage Collection
Memory and Garbage Collection
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION