Think of the heap like a busy office: quick desk cleanups are cheap, but shutting the whole office to mop every room is expensive — that is why interviewers love this question.
Question: Minor GC vs Major GC vs Full GC.
Answer: Minor GC usually means collecting the young generation, where most short-lived objects die. Major GC usually means collecting the old generation, where long-lived objects live, but the term is informal and collector-dependent. Full GC means collecting the whole heap, and often class metadata too; it is usually the longest stop-the-world pause.
Interview-Ready Answer: In Java, Minor GC typically cleans the young generation, Major GC targets the old generation, and Full GC collects the whole heap and often metaspace too. The key detail is that these names are collector-dependent, but in general Minor GC is short and frequent, Major GC is longer, and Full GC is the most expensive because it usually stops the world.
Detailed Explanation: Java objects are usually created in the young generation. The young gen is made of Eden, where new objects land, and Survivor spaces, which hold objects that survive a collection. A root is a starting point for reachability, such as a stack local, static field, or JNI reference. If an object is reachable from a root, the JVM must keep it. The generational idea is simple: most objects die young, so the JVM spends effort only where the garbage is most likely to be.
| GC type | Scope | Typical pause | Common note |
|---|---|---|---|
| Minor GC | Young gen | Milliseconds | Frequent |
| Major GC | Old gen | Longer | Collector dependent |
| Full GC | Whole heap | Longest | Often compacts |
These terms are really about pause cost and object lifetime. Minor GCs are frequent and usually cheap; Major GCs are less frequent and more expensive; Full GCs are the most painful because they usually stop all application threads and may compact memory. Compaction means moving objects together so free space becomes contiguous again.
Performance intuition: think of Minor GC as proportional to the live objects in the young gen, Major GC as proportional to the live old objects, and Full GC as proportional to the total live heap plus extra bookkeeping. In real systems, Minor GC pauses are often a few milliseconds to a few tens of milliseconds, while Full GC can easily become hundreds of milliseconds or even seconds on large heaps.
Version note: Since JDK 9, G1 has been the default HotSpot collector, and the old labels are less clean there. ZGC and Shenandoah are concurrent, low-pause collectors, so the classic Minor/Major/Full vocabulary matters less than understanding what work happens and whether the pause is stop-the-world.
Important gotcha: Major GC is not a formal Java language term. Different collectors and logs may use the words differently, so always read GC logs in the context of the collector you are using.
Real-World Story: Imagine a flash-sale checkout service. Most request objects, JSON parsing buffers, and temporary price calculations live for a few milliseconds, so the JVM mostly does Minor GCs. That is fine. Then a developer adds a static cache of request objects for debugging, and suddenly old gen starts filling with long-lived junk. Latency spikes, users see checkout timeouts, and logs start showing full GC pauses or allocation failures depending on the collector. The real bug is not Java itself; it is holding references too long, which prevents garbage collection and pushes the JVM into expensive old or full collections.
What the incident looks like: p95 latency jumps from 40 ms to 2 s, CPU may dip during long stop-the-world pauses, and error logs can mention full gc, evacuation failure, or promotion failure. That is the moment to inspect heap retention, not just add more heap blindly.
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class GcTypesDemo {
private static final class Payload {
// A small payload makes the object real enough to show reachability.
private final byte[] data = new byte[64 * 1024];
}
public static void main(String[] args) throws Exception {
printCollectors("Startup");
printHeap("Startup");
ReferenceQueue<Payload> queue = new ReferenceQueue<>();
Payload strong = new Payload();
WeakReference<Payload> weak = new WeakReference<>(strong, queue);
System.out.println();
System.out.println("Created one object with a strong reference and one weak reference.");
System.out.println("Before nulling the strong reference, weak.get() != null = " + (weak.get() != null));
strong = null; // now the object is only weakly reachable
// Create short-lived garbage to encourage a young collection.
List<byte[]> garbage = new ArrayList<>();
for (int i = 0; i < 2000; i++) {
garbage.add(new byte[1024]);
}
garbage = null; // short-lived objects become GC fodder
System.gc(); // a hint, not a guarantee
boolean collected = waitForCollection(weak, queue, 5);
System.out.println("Collected within 5 seconds? " + collected);
System.out.println("weak.get() == null = " + (weak.get() == null));
if (!collected) {
System.out.println("Edge case: the JVM may delay or ignore System.gc(); GC is not guaranteed on demand.");
}
printHeap("End");
printCollectors("End");
}
private static boolean waitForCollection(WeakReference<Payload> weak, ReferenceQueue<Payload> queue, int seconds) throws InterruptedException {
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(seconds);
while (System.nanoTime() < deadline) {
if (weak.get() == null || queue.poll() != null) {
return true;
}
// Create tiny temporary pressure so the JVM has a reason to run a young collection.
byte[] pressure = new byte[256 * 1024];
if (pressure.length == 0) {
System.out.println("Impossible");
}
Thread.sleep(25);
}
return weak.get() == null;
}
private static void printHeap(String label) {
MemoryMXBean memory = ManagementFactory.getMemoryMXBean();
MemoryUsage heap = memory.getHeapMemoryUsage();
long usedMb = heap.getUsed() / (1024 * 1024);
long committedMb = heap.getCommitted() / (1024 * 1024);
long maxMb = heap.getMax() <= 0 ? -1 : heap.getMax() / (1024 * 1024);
System.out.println(label + " heap usage: used=" + usedMb + " MB, committed=" + committedMb + " MB, max=" + maxMb + " MB");
}
private static void printCollectors(String label) {
System.out.println(label + " GC collectors:");
for (GarbageCollectorMXBean bean : ManagementFactory.getGarbageCollectorMXBeans()) {
System.out.println(" - " + bean.getName() + " | collections=" + bean.getCollectionCount() + " | time=" + bean.getCollectionTime() + " ms");
}
}
}Follow-up & Tricky Questions:
System.gc(), or cases where the JVM needs compaction or cannot continue with the current collector state.System.gc() guarantee a Full GC? No. It is only a request, and the JVM may ignore or delay it; explicit GC can even be disabled with -XX:+DisableExplicitGC.Tricky gotchas:
Common Mistakes:
Memory Hook: Nursery, storage room, whole warehouse shutdown: Minor GC empties the nursery, Major GC cleans the storage room, and Full GC closes the whole building and cleans everything.
Cheat Sheet:
Minor GC = young generation.Major GC = old generation, but the term is informal.Full GC = whole heap, often metaspace too.Practice Tasks:
-Xmx64m, and observe how often GC work happens.Payload alive and see how the weak-reference behavior changes.