English
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
| Tool | What it does | Typical moment |
|---|---|---|
jps | list Java process PIDs | always the first command |
jmap | memory histogram, heap dump | OOM, memory leaks |
jstack | thread dump, deadlock detection | stuck requests, hangs |
jstat | GC statistics sampling | watching GC behavior live |
jcmd | Swiss-army knife (JDK 8+) | everything above, one entry point |
| JMX + VisualVM | remote visual monitoring | long-running observation |
| Arthas | interactive live diagnosis | cannot restart, need answers now |
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
HashMapused 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-peakA 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.txtSearch 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=0x2d64Two 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: ifFGCkeeps 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=falseWARNING
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.
A practical triage order
jps→ find the PID.jstat -gcutil→ is it a GC problem? (if yes → GC log analysis)jstack×3 → is it a lock/latency problem?jmap -histoor heap dump → is it a memory problem?- Arthas / JMX → live follow-up questions.
Related pages
- JVM GC Metrics to Monitor — the numbers these tools sample
- JVM Performance Tuning Guide — what to change after diagnosis
- Enable GC Logging — the foundation of every analysis