Q3 of 40 · Core Java
How does garbage collection work in modern JVMs?
Short answer
Short answer: Modern JVM GC is generational: short-lived objects are collected cheaply in the Young generation (Eden + Survivor spaces); long-lived objects are promoted to Old gen and collected less frequently. G1GC (default from Java 9) targets configurable pause goals by running concurrent marking phases alongside application threads.
Detail
Java's garbage collector tracks object reachability from root references (stack frames, static fields, JNI handles). Any object not reachable from a root is eligible for collection — you don't manage memory manually.
Generational hypothesis: most objects die young. The heap is split into:
- Young generation — Eden + two Survivor spaces (S0/S1). New objects allocate in Eden. A minor GC copies live objects to a Survivor space and discards the dead. Objects that survive several collections are promoted to Old gen. Minor GCs are stop-the-world but brief because Young gen is small.
- Old generation — long-lived objects. Major GC here is more expensive and was historically the pause culprit.
- Metaspace (Java 8+) — class metadata, replaces the old PermGen.
G1GC (Garbage First, default from Java 9) divides the heap into equal-sized regions (1–32 MB) and prioritises collecting regions with the most garbage first. It runs concurrent marking phases alongside the application to minimise stop-the-world pauses, targeting a configurable pause goal (-XX:MaxGCPauseMillis, default 200ms).
ZGC and Shenandoah go further — most work happens concurrently, with pauses typically under 1ms even on multi-TB heaps, using coloured pointers or read/write barriers to update object references on the fly as objects are moved.
For QA automation, GC rarely matters at unit/integration scale. It becomes relevant in performance tests (GC pauses inflate latency percentiles), large parallel Selenium/Playwright runs under heap pressure, or when writing custom JMeter plugins. Knowing the basics helps you interpret JVM flags in CI config and diagnose test flakiness caused by GC pauses in latency-sensitive environments.
// EXAMPLE
JvmFlags.java
// JVM flags for CI test runs — tune GC for parallel test suites
// -XX:+UseG1GC (default Java 9+, explicit is fine)
// -XX:MaxGCPauseMillis=200 pause target in ms
// -Xms512m -Xmx2g fix initial/max heap to avoid resize GCs
// -Xlog:gc*:file=gc.log:time write GC log for post-run analysis
// Avoiding GC pressure in test data builders
public static List<User> buildUsers(int count) {
// Pre-size to avoid multiple ArrayList resizes
List<User> users = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
// Don't retain references beyond this scope
users.add(new User("user" + i, "user" + i + "@example.com"));
}
return users; // caller controls lifetime, no static refs
}
// Memory leak pattern to avoid in long-running test suites
private static final List<byte[]> LEAK = new ArrayList<>();
// Adding to a static list without clearing it keeps objects alive
// across test classes — causes OOM in large suites