skip to content

Walk through the columns of `jstat -gcutil` output. What does each one tell you?

level: middleimportance: should knowfreq 30%

answer

  1. S0/S1/E/O = survivor/survivor/eden/old; M/CCS = metaspace
  2. One survivor is usually ~0 (copying collector)
  3. YGC/FGC = counts; YGCT/FGCT/GCT = times — all cumulative
  4. Eden sawtooth is normal; Old ratcheting up is the leak
  5. GCT ÷ uptime ≈ GC overhead %

basics

~20 s

-gcutil shows the percentage full of each memory area — S0, S1 (survivor spaces), E (eden), O (old), M (metaspace), CCS (compressed class space) — plus counts and total times of young GCs (YGC/YGCT) and full GCs (FGC/FGCT), and the overall GC time (GCT).

solid answer

~40 s

`jstat -gcutil` prints utilization as a percentage of capacity for each region: **S0** and **S1** are the two survivor spaces (only one is in use at a time), **E** is eden (new allocations), **O** is the old/tenured generation, **M** is metaspace (class metadata), and **CCS** is the compressed class space. Then come the cumulative GC counters: **YGC** = number of young (minor) collections, **YGCT** = total seconds spent in them, **FGC** = number of full collections, **FGCT** = total full-GC seconds, and **GCT** = total GC time (YGCT + FGCT). The percentages tell you how full each region is *right now*; the counters are monotonically increasing since JVM start, so you read their *rate of change* across successive samples. Steadily rising O with frequent FGCs and a high FGCT/elapsed ratio signals real GC pressure.

go deeper

for a junior

Can identify that the letters map to memory regions and the GC count/time columns, even if shaky on survivor mechanics.

for a middle

Names every column, knows percentages are a snapshot while counters are cumulative, and reads the eden sawtooth vs an Old ratchet correctly.

for a senior

Computes GC overhead from GCT, recognizes leak shapes, and ties metaspace growth to classloader leaks; knows G1 adds concurrent-GC columns.

for a principal

Uses the output to drive heap/region sizing and collector-choice decisions and knows when -gcutil is too coarse and GC logs / JFR are needed instead.

## Reading `jstat -gcutil` `jstat -gcutil <pid> <interval>` is the most readable jstat view because every memory column is a simple **percentage of that region's current capacity** (0.00–100.00), and the rest are GC counters. To read it you need to know two things: the JVM's generational heap layout, and that the counters are *cumulative*. ### The generational heap (so the column names make sense) Modern JVMs use a **generational garbage collector** based on the *weak generational hypothesis*: most objects die young. The heap is split into: - **Young generation**, itself split into **eden** and two **survivor** spaces. - **Eden** — where new objects are allocated. When eden fills, a **minor (young) GC** runs. - **Survivor S0 / S1** — two equal halves; a minor GC copies the survivors from eden + the in-use survivor into the *other* survivor space. At any moment **one survivor is empty** (this is the copying-collector design), which is why you often see one of S0/S1 at 0.00. - **Old (tenured) generation** — objects that have survived several minor GCs are *promoted* here. When old fills, a **full/major GC** runs (expensive, often stop-the-world). - **Metaspace** — class metadata; lives in **native** memory (since Java 8 it replaced PermGen). - **Compressed class space (CCS)** — a sub-area of metaspace for compressed class pointers. ### The columns, left to right | Column | Meaning | What it tells you | |---|---|---| | **S0** | Survivor space 0 utilization (%) | How full survivor 0 is; usually one survivor is ~0 | | **S1** | Survivor space 1 utilization (%) | Same, for the other survivor | | **E** | Eden utilization (%) | Climbs as objects are allocated; drops to ~0 right after a minor GC | | **O** | Old generation utilization (%) | Long-lived data; a *steady upward trend* across samples is the leak signal | | **M** | Metaspace utilization (%) | Class metadata; a steady climb can mean a classloader leak | | **CCS** | Compressed class space utilization (%) | Sub-area of metaspace | | **YGC** | Count of **young** (minor) collections | Cumulative since JVM start | | **YGCT** | Total **time** (seconds) in young GCs | Cumulative | | **FGC** | Count of **full** (major) collections | Cumulative; frequent increments are a red flag | | **FGCT** | Total **time** (seconds) in full GCs | Cumulative; the costly part | | **GCT** | Total GC time (= YGCT + FGCT) | Cumulative; compare to wall-clock elapsed | (Some GCs/JDKs add columns like **CGC/CGCT** for concurrent-GC cycles, e.g. with G1.) ### How to actually interpret it 1. **Percentages = a snapshot.** E swinging up to ~100 then back to low is *normal* — that's the allocation/minor-GC sawtooth. Don't panic at a full eden. 2. **Counters = a rate.** Because YGC/FGC/GCT only ever grow, the meaningful quantity is *how much they grow between samples*. With a 1-second interval, if FGC jumps by several per second and FGCT grows fast, the app is spending a large fraction of its time in stop-the-world full GCs. 3. **The leak shape.** A real heap leak looks like: after each full GC, **O does not drop back down** — it ratchets upward over minutes until it pins near 100% and FGC fires constantly. That "GC churn with no reclamation" is the classic `OutOfMemoryError: Java heap space` precursor. 4. **Throughput sanity check.** Divide GCT by the process's elapsed time. A healthy app spends a small percentage of time in GC; tens of percent means GC is your bottleneck. ### Example ``` S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 98.20 61.30 45.10 96.4 91.2 120 1.840 2 0.310 2.150 ``` Reads as: survivor 1 nearly full, eden ~61% (filling toward the next minor GC), old at 45% (healthy headroom), metaspace ~96% (worth watching — may need a larger MaxMetaspaceSize), 120 minor GCs costing 1.84s total, only 2 full GCs costing 0.31s. Overall 2.15s of GC — fine if the app has been up for a long time. ### Bottom line `-gcutil` answers "how full is each region and how hard is the collector working?" in one line. Read percentages as a live snapshot and the GC counters as a rate, and the *shape over time* (especially Old refusing to fall after full GCs) tells you whether you have healthy churn or a leak.

  • Why is one of S0/S1 almost always near 0%?
    The young generation uses a copying collector. A minor GC copies live objects from eden and the currently-used survivor into the *other* survivor space, then declares the source survivor empty. So at any moment one survivor is the active 'to' space and the other is empty — hence one column near 0.
  • What does a rising M (metaspace) column with no plateau usually indicate?
    A potential classloader/metaspace leak — classes keep being loaded (e.g. dynamic proxies, repeated redeploys, or libraries generating classes) without being unloaded. Left unchecked it ends in `OutOfMemoryError: Metaspace`. The fix is finding what pins the classloaders, not just raising MaxMetaspaceSize.

saying these in an interview costs you the question

  • Panicking because E or a survivor is near 100% — that's the normal allocation sawtooth
  • Treating the cumulative counters as instantaneous rates instead of watching their delta
  • Forgetting metaspace (M/CCS) is native memory, not part of the heap
  • Assuming both survivor spaces should be populated at once — by design one is empty

context