skip to content

GC Log Analysis

Turning on unified GC logging with rotation and reading what comes out: collection cause, minor versus full, pause duration, and allocation and promotion rates. Interviewers often hand you a log and ask what is wrong, so the skill being tested is diagnosis rather than flag recall.

on this pageshow

questions

5

A HotSpot JVM prints this garbage-collection log line: `[3.216s][info][gc] GC(7) Pause Young (Normal) (G1 Evacuation Pause) 512M->96M(1024M) 14.221ms`. Walk through what each field on that line means.

level: juniorimportance: must knowfreq 55%

answer

  1. uptime · level · tags · GC(id)
  2. Pause = stop-the-world; Concurrent = not a pause
  3. before->after(capacity)
  4. "Allocation Failure" is normal, not an error
  5. cause in parentheses answers *why now*

basics

~20 s

[3.216s] is uptime, info the log level, gc the tag, GC(7) the collection's id. It was a young pause caused by G1 evacuation. Heap used went 512M before to 96M after, of 1024M total, taking 14.221 ms stop-the-world.

solid answer

~50 s

Reading left to right: `[3.216s]` is a decorator — JVM uptime when the line was written; `[info]` is the log level and `[gc]` the tag selector that produced it. `GC(7)` is the collection id, which lets you correlate every sub-line (phases, heap breakdown, CPU) belonging to the same collection. `Pause` means stop-the-world, as opposed to a `Concurrent` line that runs alongside the application. `Young (Normal)` is what was collected — the young generation, in the ordinary (not mixed, not initial-mark) flavour. `(G1 Evacuation Pause)` is the *cause*: G1 filled eden and evacuated the live objects out. Then `512M->96M(1024M)`: heap **used** before the collection, heap used after, and current heap **capacity**. So ~416M of garbage died, 96M survived. `14.221ms` is the wall-clock pause duration. Nothing here indicates a problem: this is a healthy, cheap young collection.

go deeper

for a junior

Be able to name every field in order and state that before->after(capacity) is used-heap before, used-heap after, and current capacity, and that the pause is stop-the-world.

for a middle

Add the causes you have seen and what each implies, and distinguish Pause lines from Concurrent lines when totalling pause time.

for a senior

Read a sequence rather than a line: derive interval, survivor volume and trend, and know to pull gc+cpu when Real exceeds User+Sys.

for a principal

Frame it as observability: which decorators and tags a fleet standardises on so GC lines can be correlated with request traces and incident timelines.

## Where the line comes from Since JDK 9 all HotSpot logging goes through *unified logging*, controlled by `-Xlog`. A GC line has three parts: decorators in square brackets, then the message. Decorators are chosen by you (`time`, `uptime`, `level`, `tags`, `pid`, `tid`); the default for `-Xlog:gc` is uptime, level and tags, which is exactly what the example shows. ## Field by field **`[3.216s]`** — the `uptime` decorator: seconds since JVM start. Add the `time` decorator if you need wall-clock timestamps to line up GC events with application logs or an incident timeline. Uptime alone is fine for measuring intervals between collections. **`[info]`** — the log level. GC lines you normally read are `info`; `debug` and `trace` levels expose per-phase and per-region detail and are opt-in because they are much noisier. **`[gc]`** — the tag set. Sub-systems tag their output: `gc`, `gc+heap`, `gc+cpu`, `gc+age`, `gc+ergo`, `gc+phases`. `-Xlog:gc*` selects all of them; `-Xlog:gc` selects only the top-level summary lines like this one. **`GC(7)`** — the collection sequence number, counting from 0 for the life of the JVM. Every line emitted for that collection carries the same id, so with `gc*` enabled you can gather the phase breakdown, the region counts and the CPU line for collection 7 even though they are interleaved with other output. **`Pause`** — this collection stopped every application thread. The counterpart is `Concurrent`, e.g. `Concurrent Mark Cycle`, which runs while application threads keep going and therefore is *not* a pause even though it appears in the log and takes wall-clock time. Beginners routinely add up concurrent durations and report a pause budget that never happened. **`Young (Normal)`** — the scope. In a G1 log you will also see `Young (Mixed)`, where some old regions are collected alongside the young ones, `Young (Prepare Mixed)`, `Young (Concurrent Start)`, and `Full`, which collects and compacts the entire heap. In Serial and Parallel logs the same slot reads simply `Young` or `Full`. **`(G1 Evacuation Pause)`** — the *cause*, i.e. why the JVM decided to collect now. Common causes and what they mean: - `G1 Evacuation Pause` / `Allocation Failure` — normal: a thread wanted memory and eden had none left. The word *failure* alarms people; it is the ordinary trigger of every young collection, not an error. - `G1 Humongous Allocation` — an object at least half a region in size forced a collection. - `Metadata GC Threshold` — class metadata space, not the Java heap, needed room. - `System.gc()` — application or a library called it explicitly. Worth chasing down. - `GCLocker Initiated GC` — a collection was deferred because a thread was inside a JNI critical section. - `Ergonomics` — the collector's own heuristics decided to act. **`512M->96M(1024M)`** — used-before → used-after (current capacity). The drop tells you how much died; the after value approximates the *live set* at that moment for a young collection (plus whatever old-generation data was already there). The capacity in parentheses is the currently committed heap, which can grow toward `-Xmx` or shrink; if it is far below `-Xmx`, the heap has not expanded yet. **`14.221ms`** — wall-clock duration of the stop-the-world portion. This is what your latency percentiles feel. It is *not* CPU time: the `gc+cpu` tag prints `User`, `Sys` and `Real` separately, and a Real much larger than User+Sys means the machine, not the collector, was the problem (CPU starvation, swapping, slow disk on the log write). ## Reading it as a whole One line rarely means anything; a sequence does. From consecutive lines you get the interval between collections, how much was allocated in between, how much survived each time, and whether the after-values trend upward. This single line says: at 3.2 seconds in, a routine young collection reclaimed ~416 MB in 14 ms. Healthy.

  • The cause on a line reads `(Allocation Failure)`. Does that mean the JVM is running out of memory?
    No. Allocation Failure simply means a thread requested space in eden and eden was full, which is the normal trigger for every young collection. A healthy application produces thousands of them. Genuine exhaustion shows up as repeated Full GCs that reclaim almost nothing, and ultimately as an OutOfMemoryError.
  • What is the difference between the number in parentheses and `-Xmx`?
    The parenthesised value is the currently *committed* heap capacity, which the JVM grows and shrinks between `-Xms` and `-Xmx` according to its heuristics. `-Xmx` is only the ceiling. Seeing a small capacity early in a run usually means the heap has simply not expanded yet, not that your maximum was ignored.

saying these in an interview costs you the question

  • Reading `Allocation Failure` as an error or an imminent OutOfMemoryError.
  • Treating `512M->96M(1024M)` as though the last number were the heap *used*, rather than the committed capacity.
  • Adding `Concurrent ...` durations into a pause-time budget — they do not stop application threads.
  • Assuming the duration is CPU time consumed by GC rather than wall-clock stop-the-world time.

context

open as a page

How do you enable garbage-collection logging on a HotSpot JVM running JDK 9 or later, and which options control the log destination, the level of detail, and file rotation?

level: middleimportance: must knowfreq 60%

basics

~10 s

Use the unified logging flag -Xlog. Its four colon-separated parts are selectors, output, decorators, output options: -Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=10,filesize=20M. It replaced -XX:+PrintGCDetails and -Xloggc, and is cheap enough to leave on in production.

open as a page

Reading a HotSpot garbage-collection log, how do you tell a young (minor) collection from a mixed collection and from a full collection, and why is spotting a full collection important?

level: middleimportance: must knowfreq 58%

basics

~20 s

Young collections log Pause Young, G1's mixed ones Pause Young (Mixed) (young plus some old regions), and full ones Pause Full, which collects and compacts the whole heap in one long stop. Under G1 or a concurrent collector, a Full GC signals the concurrent machinery failed to keep up.

open as a page

A JVM service normally shows 15 ms garbage-collection pauses but occasionally stops for nearly a second. Working only from GC logs, how do you find out what causes the outliers?

level: seniorimportance: must knowfreq 50%

basics

~20 s

Sort collections by duration, then read the outliers' lines: their scope and cause, whether To-space exhausted or a Full GC preceded them, the phase breakdown from gc+phases, the gc+cpu User/Sys/Real split, and safepoint timings. Cause first, then flags.

open as a page

How do you compute an application's allocation rate and promotion rate from a HotSpot garbage-collection log, and what do those two numbers tell you?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Allocation rate = (heap used before a collection − heap used after the previous one) ÷ the interval between them, averaged over many collections. Promotion rate = old-generation growth per collection ÷ the same interval. High allocation means frequent young pauses; high promotion means old-generation pressure and eventual long collections.

open as a page