English
GCViewer: Offline GC Log Analysis and Tuning
GCViewer is one of the oldest GC log visualizers in the JVM world: a single jar, pure Java desktop app, LGPL licensed. Double-click it and you get the heap sawtooth plus dozens of statistics — the log never leaves your machine.
EasyGC reads the same log, just in a different shape: EasyGC runs in the browser (no install, leak detection and a recommendation engine), GCViewer fits air-gapped networks where logs cannot be shared, batch/CI runs that need structured output, and digging through raw GC events one by one. Their conclusions can be cross-checked against each other.
1. Install and launch
Current release: 1.37 (published 2026-05-25).
| Where | Link |
|---|---|
| Executable jar | https://sourceforge.net/projects/gcviewer/files/gcviewer-1.37.jar/download |
| macOS app bundle | gcviewer-1.37-dist-mac.zip (same SourceForge folder) |
| Maven Central | com.github.chewiebug:gcviewer:1.37 |
First gotcha
The GitHub release page has no attached binaries — only links to SourceForge. If you cannot find the jar, that is why.
You need a Java 8+ VM to run it:
bash
java -jar gcviewer-1.37.jar # start the GUI
java -jar gcviewer-1.37.jar gc.log # start the GUI and load this logBuild from source (Maven 3, output lands in target/):
bash
git clone https://github.com/chewiebug/GCViewer.git
cd GCViewer && mvn clean installReuse its parser in your own tooling via Maven Central:
xml
<dependency>
<groupId>com.github.chewiebug</groupId>
<artifactId>gcviewer</artifactId>
<version>1.37</version>
</dependency>2. Produce a log GCViewer can parse
How much you see depends entirely on the flags you turned on.
| JDK | Recommended flags | Note |
|---|---|---|
| Java 9+ (unified logging) | -Xlog:gc=info:file="gc.log":tags,uptime,level | the documented minimum |
| Java 9+ (recommended) | -Xlog:gc*:file=gc.log:tags,time,uptime,level | most complete — phases and concurrent cycles included |
| Java 9+ (maximum) | -Xlog:gc*=trace:file="gc.log":tags,time,uptime,level | extra tags are ignored |
| Oracle JDK 6 / 7 / 8 | -Xloggc:gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps | the best-practice combination |
| Sun JDK 1.4 / 1.5 | -Xloggc:gc.log -XX:+PrintGCDetails | |
| Sun JDK 1.2.2 / 1.3.1 | -verbose:gc | no timestamps → no Total Time / Throughput |
| IBM JDK 1.2.2 – 1.4.2 | -verbose:gc | make sure events are not wrapped |
| HP-UX JDK 1.2 – 1.4 | -Xverbosegc | |
| BEA JRockit 1.4.2 – 1.6 | -verbose:memory -Xverbose:gcpause,gcreport |
Two extra flags decide how many metrics you get:
-XX:+PrintGCDetails— without it you lose the young/tenured areas, Promotion and InitiatingOccFraction. On Java 9+,-Xlog:gc*already covers this.-XX:+PrintGCApplicationStoppedTime— required for VM operations overhead, the safepoint time that is not GC (biased-lock revocation, thread dumps, class redefinition).
Version difference
In 1.36 and earlier, extra decorations in unified logging break parsing; 1.37 supports all of them. If an older build refuses your -Xlog file, narrow the decorations to tags,time,uptime,level.
3. Reading the GUI
Opening a log
File → Open File for one file; File → Open Series for rotated logs (gc.log.0…gc.log.N read as one continuous log); File → Open URL accepts a URL directly.
Two handy switches during a load test:
File → Watch— reloads automatically whenever the file changes;View → show datestamps— toggles between seconds since log start and absolute timestamps.
The chart
| Legend | Meaning | What it tells you |
|---|---|---|
| Total Heap (red line) | allocated heap size | whether the heap keeps growing |
| Used Heap (blue line) | actual occupancy | sawtooth = healthy; rising valley floors = leak |
| Young / Tenured Generation (orange / magenta areas) | per-generation size (needs PrintGCDetails) | how far old gen is from its ceiling |
| Used young / used tenured heap | live data per generation | where survivors concentrate |
| Full GC Lines (black) | every Full GC | density = Full GC frequency |
| Inc GC Lines (cyan) | every incremental GC | |
| GC Times Line (green) | pause trend | pauses getting longer or steady? |
| GC Times Rectangles | per-pause duration, color-coded for Full GC / initial-mark / remark / VM operation / normal / incremental GC | which event type your long pauses come from |
| Initial mark level (yellow) | heap level when the concurrent cycle starts (CMS / G1) | is it triggered too late? |
| Concurrent collections (cyan begin / pink end) | concurrent cycles | do cycles finish before the next one starts? |
Every layer can be toggled in the View menu; 1.37 adds scroll-wheel zoom; right-clicking the time ruler lets you set the absolute start time (auto-filled when the log carries PrintGCDateStamps).
Event details
- Gc pauses — stop-the-world pauses that are not Full GCs;
- Full gc pauses — collections spanning generations or named full gc;
- VM operations overhead (safepoint pauses) — safepoint time beyond GC (needs
PrintGCApplicationStoppedTime); - Concurrent GCs — concurrent cycle information.
Check the Parser tab first
The neighbouring Parser tab lists warnings from the parsing run. Any error means lines were dropped and the statistics are incomplete — fix that before trusting a number.
The data panel (View → Data Panel)
Summary
| Metric | Meaning | What to look for |
|---|---|---|
| Footprint | maximum heap used in the window | the minimum memory this box needs |
| Total Time | duration covered by the log | a couple of minutes is not a baseline |
| Throughput | app time share = 1 − accumulated pauses / total time | > 95% healthy, < 90% tune it |
| Accumulated pauses / GC / full GC | cumulative pause per category | the Full GC row should be near zero |
| Freed Memory / Freed by GC / Freed by full GC | bytes reclaimed | |
| Freed Mem/Min | bytes reclaimed per minute | a rough allocation rate |
| GC Performance / Full GC Performance | bytes reclaimed per unit of time | collection efficiency |
| Slope GC / Slope full GC | growth slope of heap after GC | persistently positive = leak signal |
| Avg rel inc after GC / after FGC | relative heap increase after each GC | the same signal from another angle |
Memory
| Metric | Meaning |
|---|---|
| Total / Tenured / Young / Perm heap (usage / alloc. max) | usage vs. allocation ceiling per generation |
| Avg / Max heap after conc GC | heap occupancy after concurrent cycles (the floor for Footprint) |
| Avg / Max heap after full GC | heap occupancy after Full GC — close to Footprint means you are out of capacity |
| Avg / Max tenured after conc GC | old-gen level |
| Avg / Total promotion | promotion volume (needs PrintGCDetails) — how much survives each Young GC |
| InitiatingOccFraction (avg / max) | old-gen occupancy when the concurrent cycle fires (the basis for -XX:CMSInitiatingOccupancyFraction) |
Pause
| Metric | Meaning |
|---|---|
| Number of pauses / gc pauses / full gc pauses | pause counts |
| Accumulated / Avg / Min / Max Pause | cumulative, average, minimum and maximum pause |
| Avg pause interval / Min / max pause interval | interval between pauses — a better frequency signal than raw counts |
| The same set for full gc pauses | Full GC in isolation |
How to read them
Max Pause maps straight onto your SLO ("P99 < 200 ms"); an unusually small Avg pause interval means GC thrashing; Total pause / Total Time is the mirror image of throughput.
4. Command line: batch and CI
No argument (or only a log) starts the GUI — two or more arguments switch to command-line mode:
bash
# report only (defaults to -t SUMMARY)
java -jar gcviewer-1.37.jar gc.log summary.csv
# report + heap chart
java -jar gcviewer-1.37.jar gc.log summary.csv chart.png
# pick the export type
java -jar gcviewer-1.37.jar gc.log summary.csv chart.png -t CSV_TS
# rotated logs: join them with semicolons
java -jar gcviewer-1.37.jar "gc.log.0;gc.log.1;gc.log.2;gc.log.current" summary.csv| Position | Argument | Required | Meaning |
|---|---|---|---|
| 1 | <gc-log-file|url> | yes | one log, a semicolon-separated rotation series, or a URL |
| 2 | <export.csv> | yes | report output path |
| 3 | [chart.png] | no | also render the heap chart |
| — | -t <type> | no | export type, defaults to SUMMARY |
Choosing an export type:
| Type | Output | Best for |
|---|---|---|
SUMMARY | full summary (everything in the GUI data panel) | human review, archiving |
CSV | comma separated, aggregated | spreadsheets |
CSV_TS | one row per GC event with a unix timestamp | pipelines, trend charts, diffing two tuning runs |
PLAIN | plain data (HPjmeter compatible) | feeding HPjmeter |
SIMPLE | <event name> <seconds since start> <pause> | feeding gchisto |
Exit codes (straight from the source constants), useful in CI:
| Code | Meaning |
|---|---|
0 | success |
-1 | export failed |
-2 | argument parsing failed (usage is printed) |
-3 | more than three positional arguments (usage is printed) |
Batch-processing a night of logs:
bash
for f in /var/log/app/gc.log.*; do
java -jar gcviewer-1.37.jar "$f" "${f}.csv" "${f}.png" -t CSV_TS || echo "FAILED: $f"
doneShell escaping
The semicolon separates log files — quote it, or your shell treats it as the end of the command.
5. Four tuning paths
Walking these four paths beats staring at every number.
1. Low throughput (Throughput < 95%)
Check Throughput and Accumulated pauses in Summary → open the Pause tab and look at Number of gc pauses and Avg pause interval.
- Many pauses, short intervals, each one short → young gen too small: raise
-Xmnor lower-XX:NewRatio; - A few very long ones → that is path 3.
Verify: throughput up, Avg pause interval up (GC gets rarer).
2. Frequent Full GC / old gen saturated
Look at the density of Full GC Lines → compare Max heap after full GC with Footprint in the Memory tab:
- close together → out of capacity: raise
-Xmx, review cache sizes and data loading; - far apart yet Full GCs keep coming → usually
System.gc()or metaspace; check the reason in Event details; - the blue Used Heap line has rising valley floors and Slope GC is positive → memory leak: GCViewer can go no further, switch to
jmap+ MAT (see JVM Diagnostic Tools).
3. A single pause breaks the SLO
Check Max Pause → find the matching GC Times Rectangles color to identify the event type:
- black (Full GC) → back to path 2;
- remark / initial-mark (CMS) → the concurrent cycle starts too late: lower
-XX:CMSInitiatingOccupancyFraction, or move to G1; - G1 with a long tail → set
-XX:MaxGCPauseMillis, look for humongous allocations (raise-XX:G1HeapRegionSize); - extremely latency-sensitive → ZGC / Shenandoah (1.37 improved parsing for both).
4. Promotion pressure: a quietly fattening old gen
Watch Avg / Total promotion and Max tenured after conc GC:
- high promotion plus a rising old gen → the young gen is too small (premature promotion) or Survivor overflows: enlarge the young gen, tune
-XX:SurvivorRatio, raise-XX:MaxTenuringThreshold; - high promotion but a stable old gen → normal, just keep watching.
5. The easy misdiagnosis
When VM operations overhead clearly exceeds the GC pauses, this is not a GC problem — it is other safepoint work (biased-lock revocation, thread dumps, class redefinition). Heap flags will not help.
6. GCViewer or EasyGC?
| GCViewer | EasyGC | |
|---|---|---|
| Shape | desktop jar, needs Java 8+ | web page, opens instantly |
| Where the log goes | local | local (parsed in the browser, never uploaded) |
| Charts | heap / generations / pause rectangles, zoom, layer toggles | heap usage sawtooth, pause distribution, GC cause donut |
| Event details | individual events + parser warnings | full event table |
| Leak detection | Slope / chart valley floors (manual) | linear regression with an automatic verdict |
| Tuning advice | none | 15 rules with four severity levels |
| Batch / CI | command line, scriptable | manual upload |
| Structured export | CSV / CSV_TS / PLAIN / SIMPLE / PNG | PDF / Word / JSON |
| Intranet deployment | copy the jar | static site, also deployable internally |
A good combination: EasyGC first for the verdict and recommendations, GCViewer to double-check when you need offline work, batch runs or raw events. Two tools, one log, far more confidence.
7. Known traps
- Download location: the jar lives on SourceForge, not in GitHub release assets.
- Older versions choking on unified logs: extra decorations break parsing before 1.37 — upgrade or trim them.
- CMS / G1 mixing concurrent events with STW pauses: the README states the parser sometimes cannot recover — a known limitation.
- Huge logs: GCViewer is a Java program too — run it with
java -Xmx2g -jar gcviewer.jarfor several-hundred-MB logs. - Rotated logs: semicolons on the command line (quoted),
File → Open Seriesin the GUI. - Missing metrics: no
PrintGCDetails→ no promotion, no generation areas; noPrintGCApplicationStoppedTime→ no safepoint overhead. - Never ignore parser warnings: with unparsed lines, every statistic describes only part of the log.
Related pages
- JVM Performance Tuning Guide — which knob to turn and how to verify
- Understanding the Report — cross-check GCViewer with EasyGC
- Enabling GC Logging — the foundation of every analysis
- JVM Diagnostic Tools — where to go beyond GC