Hook: Garbage Collection is Java's built-in cleanup crew — interviewers love it because it reveals whether you understand memory, pauses, and leaks, not just syntax.
Question: Explain Garbage Collection.
Answer: Garbage Collection, or GC, is the JVM's automatic process for finding objects on the heap that your program can no longer reach and reclaiming their memory. An object becomes eligible for GC when no live reference can get to it from a GC root, such as a local variable, static field, or active thread. This helps Java avoid many manual memory bugs, but it does not mean memory is released at a predictable instant.
Interview-Ready Answer: In Java, Garbage Collection is the JVM's automatic memory management for heap objects. I think of it as the JVM tracing which objects are still reachable from GC roots like stack variables and static fields; anything unreachable becomes eligible for reclamation. The important interview point is that GC is automatic but nondeterministic — for example, System.gc() is only a hint, and the collector choice, such as G1, affects pause times and throughput.
Detailed Explanation:
Java objects live on the heap, which is memory managed by the JVM. GC is a tracing collector: it starts from known live entry points, called GC roots (for example, thread stacks, static fields, and JNI references), and follows references to mark everything that is still reachable. Anything not marked is considered garbage.
TLAB (thread-local allocation buffer), which is a small private chunk for each thread, so object allocation is often close to constant-time and very fast.stop-the-world (STW), meaning your program threads are temporarily paused while some GC work happens.Most HotSpot collectors use a generational layout. The young generation holds newly created objects and is collected often; the old generation holds long-lived objects and is collected less frequently. That design is practical because many temporary objects die within milliseconds, so reclaiming them early is cheaper than scanning a huge heap every time.
| Collector | Goal | Pause style | Good for |
|---|---|---|---|
| Serial | Simple | Single-threaded STW | Small heaps |
| Parallel | Throughput | Multi-threaded STW | Batch jobs |
| G1 | Balanced | Mostly brief STW | General servers |
| ZGC | Low pause | Mostly concurrent | Large heaps |
In modern HotSpot, G1 is the default general-purpose collector for most server-style workloads since JDK 9. Older JDK 8 server configurations commonly used Parallel GC. Low-pause collectors like ZGC aim for very short pauses, but they usually spend more CPU coordinating work concurrently.
O(1), while collection work grows with the amount of live data that must be scanned or copied.-Xms sets initial heap size and -Xmx sets maximum heap size. The JVM uses ergonomics to choose defaults from available memory, and modern versions respect container limits.Memory model shortcut: think reachable = alive, unreachable = garbage. That is the core rule interviewers want you to say clearly.
Real-World Example: Imagine an e-commerce checkout service on Black Friday. Every request creates cart objects, discount calculations, and payment data. A developer adds a static map as a quick cache and forgets to evict old entries, so finished carts stay strongly reachable forever.
GC overhead limit exceeded or repeated allocation-failure pauses.The lesson is important: a GC problem is often really a reference-retention bug. The JVM is doing the right thing; the application is accidentally holding on to objects that should have become unreachable. In production, that difference decides whether you fix the heap size, the collector, or the cache design.
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
public class GarbageCollectionDemo {
// A static list is a classic source of accidental memory retention:
// as long as this list holds an object, the GC must treat it as reachable.
private static final List<Payload> STATIC_CACHE = new ArrayList<>();
private static final class Payload {
private final String name;
private final byte[] data;
private Payload(String name, int kilobytes) {
this.name = name;
this.data = new byte[kilobytes * 1024];
}
@Override
public String toString() {
return name + " (" + data.length + " bytes)";
}
}
public static void main(String[] args) throws Exception {
demonstrateCollectibleObject();
System.out.println();
demonstrateRetainedObject();
}
private static void demonstrateCollectibleObject() throws InterruptedException {
System.out.println("=== Collectible object ===");
ReferenceQueue<Payload> queue = new ReferenceQueue<>();
Payload payload = new Payload("temporary", 256);
WeakReference<Payload> weak = new WeakReference<>(payload, queue);
// Remove the strong reference; now only the weak reference remains.
// WeakReference does not keep the object alive.
payload = null;
forceGc();
report(weak, queue, "temporary");
}
private static void demonstrateRetainedObject() throws InterruptedException {
System.out.println("=== Retained by a strong reference ===");
ReferenceQueue<Payload> queue = new ReferenceQueue<>();
Payload payload = new Payload("cached", 256);
WeakReference<Payload> weak = new WeakReference<>(payload, queue);
// This strong reference prevents collection.
// GC cannot reclaim a live object, even if it is only held by a cache.
STATIC_CACHE.add(payload);
payload = null;
forceGc();
report(weak, queue, "cached");
// Cleanup so the demo does not keep growing if run repeatedly.
STATIC_CACHE.clear();
}
private static void forceGc() throws InterruptedException {
// System.gc() is only a hint, not a command.
// We retry a few times and add small allocation pressure to make collection more likely.
for (int i = 0; i < 5; i++) {
System.gc();
byte[] pressure = new byte[1024 * 1024];
pressure[0] = 1;
Thread.sleep(100);
}
}
private static void report(WeakReference<Payload> weak, ReferenceQueue<Payload> queue, String label) {
Payload current = weak.get();
if (current == null) {
System.out.println(label + ": collected");
} else {
System.out.println(label + ": still reachable -> " + current);
}
System.out.println("ReferenceQueue enqueued? " + (queue.poll() != null));
System.out.println("Key idea: only unreachable objects are eligible for GC; strong references keep them alive.");
}
}
Follow-up & Tricky Questions:
WeakReference does not keep an object alive, so the GC can clear it as soon as there are no strong references. That is why weak references are useful for caches that should not prevent cleanup.System.gc() force collection? No; it is only a hint to the JVM. The JVM may ignore it or delay action, so you should never depend on it for correctness or performance.try-with-resources.AutoCloseable instead.Common Mistakes:
System.gc() guarantees cleanup. Correction: It is only a hint, and the JVM can ignore it.Memory Hook: Think of GC as a librarian who removes books only when no reader can reach them anymore. If a bookmark, hand, or shelf label still points to the book, it stays.
Cheat Sheet:
G1 is the common default in modern HotSpot.System.gc() is a hint, not a command.Practice Tasks:
List cache to a small program, forget to clear it, and observe rising memory usage.-Xlog:gc and read the pause messages to connect theory with real JVM behavior.