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?
answer
- -Xlog:<what>:<where>:<decorators>:<opts>
- gc* for always-on; gc+heap, gc+age, gc+cpu, safepoint for digging
- filecount + filesize = bounded rotation; filecount=0 = unbounded
- %p / %t in the filename survive restarts
- PrintGCDetails / -Xloggc are gone since JDK 9's unification
basics
~10 sUse 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.
solid answer
~50 sEverything goes through `-Xlog`, whose grammar is `-Xlog:<what>:<where>:<decorators>:<output-options>`. ``` -Xlog:gc*:file=/var/log/app/gc-%p.log:time,uptime,level,tags:filecount=10,filesize=20M ``` - **what**: tag selectors with levels — `gc` for one summary line per collection, `gc*` for all GC sub-tags at info, plus targeted ones such as `gc+heap=debug` (per-generation/region occupancy), `gc+cpu` (User/Sys/Real), `gc+age=trace` (tenuring distribution), `gc+ergo*` (why the collector decided what it did). `safepoint` is a separate tag worth adding when hunting pauses. - **where**: `file=...`, or `stdout`/`stderr`. `%p` and `%t` expand to pid and start timestamp so restarts don't collide. - **decorators**: always include `time` (wall clock, to correlate with request logs) alongside `uptime`, `level`, `tags`. - **output options**: `filecount`/`filesize` give bounded rotation; `filecount=0` disables rotation and lets one file grow forever. Overhead at info level is well under a percent, so it is standard to run it always-on — a pause you did not log is a pause you cannot explain.
go deeper
Know that -Xlog:gc*:file=gc.log is how you turn GC logging on today and that the old Print* flags are gone.
Recite the four-part grammar, name the tags you would add for a specific investigation, and configure rotation correctly.
Argue for always-on logging with wall-clock decorators, restart-safe paths and container-aware destinations, and know when to reach for the safepoint tag.
Treat GC logging as fleet-wide telemetry policy: standard flag set in the base image, logs shipped and retained, so any incident is analysable after the fact rather than reproduced.
## What replaced what Before JDK 9, GC logging was a pile of ad-hoc flags: `-XX:+PrintGCDetails`, `-XX:+PrintGCTimeStamps`, `-XX:+PrintGCDateStamps`, `-XX:+PrintTenuringDistribution`, `-Xloggc:file`, `-XX:+UseGCLogFileRotation`, `-XX:NumberOfGCLogFiles`, `-XX:GCLogFileSize`. JDK 9 introduced *unified logging*, which routes every JVM subsystem through one flag with one grammar, and the old GC-print flags were deprecated and subsequently removed. On a modern JDK a startup script still carrying `-XX:+PrintGCDetails` will either warn or refuse to start — a very common upgrade snag. ## The grammar ``` -Xlog:<selectors>:<output>:<decorators>:<output-options> ``` All parts after the selectors are optional, and you can repeat `-Xlog` for multiple destinations. **Selectors** are `tag[+tag...][*][=level]` pairs, comma-separated. Tags name the subsystem (`gc`, `heap`, `cpu`, `age`, `ergo`, `phases`, `region`, `ref`, `start`, `metaspace`, `safepoint`). `gc` alone matches only lines tagged exactly `gc`; `gc*` matches every tag set containing `gc`. Levels are `error`, `warning`, `info`, `debug`, `trace`, defaulting to `info`. **Output** is `stdout` (default), `stderr` or `file=<path>`. In the path, `%p` expands to the process id and `%t` to the JVM start time — essential when a supervisor restarts the process, otherwise the new JVM truncates the log that holds the evidence for why the old one died. **Decorators** prefix each line. `uptime`, `level`, `tags` are the default for GC output; add `time` for an ISO-8601 wall-clock stamp so GC events can be correlated with request traces and alerts. `pid`/`tid` are useful when several JVMs share a log sink. **Output options** control rotation: `filesize=20M` and `filecount=10` keep ten files of twenty megabytes, rotating in a ring. `filecount=0` means *no rotation* — a single file that grows until the disk fills. That is a genuine production incident, not a theoretical one. ## Choosing the detail level - `-Xlog:gc` — one line per collection. Enough to see pause counts and durations. - `-Xlog:gc*` — the standard always-on setting: adds heap breakdown, phase timings, CPU accounting, and the collector's own decisions. - `-Xlog:gc+heap=debug` — occupancy per generation or per region class after each collection; needed to compute promotion rate. - `-Xlog:gc+age=trace` — the tenuring distribution: how many bytes sit at each survivor age. The evidence for premature promotion. - `-Xlog:gc+cpu` — `User`, `Sys` and `Real` per collection. Real ≫ User+Sys means the host was starved, swapping, or the log write itself blocked. - `-Xlog:safepoint` — time *to reach* a safepoint versus time *at* the safepoint. A long stop that GC lines cannot explain is usually time-to-safepoint or a non-GC safepoint operation, which the `gc` tags never show. ## Overhead and production practice At `info` the cost is a formatted line or two per collection: microseconds of work, unmeasurable against the collection itself, well under a percent of throughput in any realistic workload. `debug`/`trace` selectors are noisier but still modest; the real risk is disk volume, which rotation bounds. The professional default is: GC logging on in every environment, always, with rotation, wall-clock decorators, on a path that survives process restarts and (in containers) is either on a mounted volume or shipped to a log collector — an ephemeral container filesystem loses exactly the log you need after an OOM-kill. ## Common mistakes - Carrying legacy flags forward after a JDK 8 → 17/21 upgrade. - Forgetting `filecount`, so a long-lived JVM writes one unbounded file. - Logging to a path inside the container that vanishes on restart. - Turning logging on only *after* an incident, guaranteeing you cannot analyse the incident that already happened. - Enabling `trace` on everything and drowning the signal. ## Version note The `-Xlog` grammar is stable from JDK 9 onward; specific tags gain and lose sub-detail across releases, and each collector emits its own vocabulary, so always confirm against `-Xlog:help` on the actual JDK you run.
- Can GC logging safely be left enabled in production?Yes, and it should be. At info level it costs a handful of formatted lines per collection — far below a percent of throughput — while rotation bounds disk use. The alternative is diagnosing a latency incident with no record of what the collector did, which usually means waiting for it to happen again.
- Your GC log shows no collection near a 400 ms stall the application reported. Where do you look next?At safepoints: add `-Xlog:safepoint`. Stop-the-world time has two parts, reaching the safepoint and being at it, and long time-to-safepoint (a counted loop, a slow-to-yield thread) produces a stall with no GC line. Non-GC safepoint operations such as biased-lock revocation, deoptimisation or heap inspection also stop the world without touching the gc tags.
- How would you set this up inside a container?Write to a mounted volume or to stdout picked up by the log collector, include `%p`/`%t` in any file path, and keep `filecount`/`filesize` bounded so the writable layer cannot fill. The critical case is an OOM-killed container: if the log lived only on the ephemeral filesystem, the evidence dies with the container.
saying these in an interview costs you the question
- Still recommending `-XX:+PrintGCDetails -Xloggc:gc.log` on a modern JDK, where those flags no longer exist.
- Claiming GC logging is too expensive for production and should be enabled only when a problem appears.
- Configuring `file=` with no `filecount`/`filesize`, producing an unbounded log.
- Assuming `-Xlog:gc` and `-Xlog:gc*` are equivalent — the first omits heap, CPU, phase and ergonomics detail.
- Expecting GC tags to explain every stop-the-world pause, ignoring safepoint operations.