Skip to content

JVM Diagnostic Tools: jmap, jstack, jstat & Arthas ​

It is 2 AM. The on-call phone rings: one service is throwing OutOfMemoryError, another responds every 30 seconds, and a third burns 100% CPU. Every JDK ships with the tools you need for all three — this guide walks through them by scenario, not by alphabet.

The toolbox at a glance ​

ToolWhat it doesTypical moment
jpslist Java process PIDsalways the first command
jmapmemory histogram, heap dumpOOM, memory leaks
jstackthread dump, deadlock detectionstuck requests, hangs
jstatGC statistics samplingwatching GC behavior live
jcmdSwiss-army knife (JDK 8+)everything above, one entry point
JMX + VisualVMremote visual monitoringlong-running observation
Arthasinteractive live diagnosiscannot restart, need answers now
GCVieweroffline GC log visualization + batch exportpost-mortem, on-premise / CI

Scenario 1: the service died with OutOfMemoryError ​

Goal: find which objects ate the heap.

The most reliable evidence is a heap dump captured at the moment of death. Plant the trap in advance:

bash
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/dump/

When the OOM strikes, the JVM writes a .hprof file automatically. Open it in Eclipse MAT or VisualVM and look at the dominator tree: one or two objects usually hold most of the heap. Classic culprits from real incidents:

  • an unbounded HashMap used as a cache with no eviction policy,
  • a thread-local list that never gets cleaned in a thread pool,
  • a query result loaded into memory without pagination (a "small" report query over 8 million rows).

Need a quick answer without downloading the dump? The histogram lists instances and bytes per class:

bash
jmap -histo <pid> | head -30        # all objects
jmap -histo:live <pid> | head -30   # live only — triggers a Full GC, use off-peak

A business class appearing thousands of times in the top rows usually points straight at the bug.

Scenario 2: requests hang, the service is frozen ​

Goal: find out what the threads are waiting for.

bash
jstack <pid> > thread-dump.txt

Search the dump for BLOCKED states and for the line Found one Java-level deadlock — the JVM detects classic lock cycles for you and prints the two locks involved. The typical root cause is inconsistent lock ordering: method A locks resource 1 then 2, method B locks 2 then 1. The fix is to always acquire locks in the same global order — or better, replace hand-rolled synchronization with java.util.concurrent primitives.

Take three dumps a few seconds apart: a thread that stays BLOCKED on the same lock across all three is your stuck request; a thread whose stack changes is just slow, not stuck.

Scenario 3: CPU at 100% ​

Goal: find the one thread burning a core.

bash
top -Hp <pid>              # list threads of the JVM, note the top thread id
printf '%x\n' <tid>        # convert to hex, e.g. 0x2d64
# then search the jstack dump for nid=0x2d64

Two common outcomes:

  • the thread's stack shows your code in a loop — a real hot loop bug;
  • the top consumers are GC threads — the "CPU problem" is actually a GC problem. Confirm with jstat -gcutil <pid> 1000: if FGC keeps climbing, continue with a GC log analysis.

Scenario 4: watch GC behavior live with jstat ​

jstat samples GC counters without stopping anything:

bash
jstat -gcutil <pid> 1000 10   # every 1s, 10 samples, percentages per generation
jstat -gc <pid> 1000 10       # absolute KB values

-gcutil shows Eden / Survivor / Old / Metaspace occupancy as percentages plus YGC, FGC and their accumulated times. From a short series you can estimate:

  • young-gen fill rate — how fast Eden fills between samples → predicts Young GC frequency;
  • average Young GC time — YGCT / YGC;
  • promotion pressure — how much the Old gen grows after each Young GC;
  • Full GC cost — FGCT / FGC.

That estimation workflow is exactly what the EasyGC report automates from a full GC log — with charts and leak detection on top.

Scenario 5: watch a remote JVM visually ​

For long-running observation, connect VisualVM or another JMX client over the network. The target JVM needs:

bash
-Dcom.sun.management.jmxremote.port=8888
-Djava.rmi.server.hostname=<server-ip>
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false

WARNING

authenticate=false exposes the JVM to anyone who can reach the port. Bind to an internal interface, use a firewall, and never expose JMX to the public internet.

The Visual VM plug-in "Visual GC" renders the generations in real time — a great way to feel the allocation pattern of an application during a load test.

Scenario 6: you cannot restart, you need answers now ​

Arthas (Alibaba, open source) attaches to a running JVM and offers an interactive shell: dashboard for a live overview, thread -b to locate deadlocks directly, jad to decompile a class and check which version actually runs, profiler for flame graphs of hot paths. When a production issue cannot wait for a redeployment, it is the fastest path from symptom to root cause.

Bonus: replay a GC log offline with GCViewer ​

Every tool above samples the live scene. For a real post-mortem — before/after a release, a load test or a flag change — you need the full GC log: jstat gives you a few dozen samples, the log has every single event.

GCViewer is a single-jar local tool for exactly that, which matters when the log cannot leave the machine:

bash
java -jar gcviewer-1.37.jar gc.log summary.csv chart.png -t SUMMARY   # report + heap chart
java -jar gcviewer-1.37.jar gc.log events.csv -t CSV_TS               # one row per event, easy to diff
java -jar gcviewer-1.37.jar "gc.log.0;gc.log.1;gc.log.2" summary.csv   # rotated logs as one continuous log

It reads the same log as EasyGC: EasyGC gives you the verdict and recommendations in the browser, GCViewer covers on-premise work, batch runs and per-event inspection. Full walkthrough: GCViewer Usage Guide.

A practical triage order ​

  1. jps → find the PID.
  2. jstat -gcutil → is it a GC problem? (if yes → GC log analysis, or replay offline with GCViewer)
  3. jstack ×3 → is it a lock/latency problem?
  4. jmap -histo or heap dump → is it a memory problem?
  5. Arthas / JMX → live follow-up questions.