How does Java handle memory leaks?

Java prevents most memory leaks through automatic garbage collection, which reclaims memory for objects no longer reachable from any live thread. However, garbage collection only reclaims what's truly unreachable, so leaks still happen when code unintentionally keeps references to objects it no longer needs.

Key Points: • The garbage collector cannot free an object as long as something still references it, even if the application logically no longer needs it. • Common leak sources include static collections that grow without eviction, unclosed resources like streams or connections, and listeners or callbacks that are registered but never unregistered. • try-with-resources is the standard way to guarantee streams and connections are closed even when exceptions occur, preventing native and file-handle leaks. • Caches should use bounded or weak-reference-based structures, like WeakHashMap or a proper caching library, rather than plain maps that grow forever. • Monitoring heap trends and periodically profiling long-running services helps catch leaks before they cause OutOfMemoryError in production.

Example: A web application that registers event listeners on every request but never removes them will accumulate listener objects indefinitely, since the listener registry still holds live references even after the request completes.

Code Example:

try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
    String line = reader.readLine();
} // reader is closed automatically, even if an exception is thrown

Interview Tip: A concise interview answer is:

"Java's garbage collector automatically reclaims unreachable objects, but it can't help if code keeps unnecessary references alive, like a growing static cache or an unregistered listener. To avoid leaks I use try-with-resources for anything closeable, bound or evict caches, and periodically profile long-running services to catch abnormal heap growth early."