JVM Production Troubleshooting Handbook
This skill is organized by "symptom -> diagnostic workflow -> root cause table -> fix", covering 7 categories of high-frequency JVM failures. All commands require first running jps -lvm to obtain the <pid>.
Boundary with performance-tuning:
- jvm-troubleshoot (this skill): reactive troubleshooting (OOM/CPU 100% has already occurred) -> follow "symptom -> diagnosis -> fix"
- performance-tuning: proactive optimization (selecting GC before deployment, tuning JVM parameters, writing high-performance code) -> follow "layer -> selection -> optimization -> verification"
Failure Category Quick Reference
| Symptom | Jump to |
|---|---|
Application reports OutOfMemoryError |
OOM Troubleshooting |
| CPU usage at 100% | CPU Troubleshooting |
| Frequent GC, application lag | High-Frequency GC Troubleshooting |
| Slow response, timeout, no error | Performance Troubleshooting |
| Thread deadlock, program stuck | Thread Deadlock Troubleshooting |
ClassNotFoundException / NoClassDefFoundError |
Class Loading Troubleshooting |
InaccessibleObjectException / module system reflection failure |
Module System Troubleshooting |
| JVM startup failure, immediate exit | Startup Failure Troubleshooting |
JVM killed inside container / jps cannot see / JRE-only |
Container Troubleshooting |
OOM Troubleshooting
OOM types and identification:
| OOM Type | Error Keyword | Root Cause | Troubleshooting |
|---|---|---|---|
| Java heap space | OutOfMemoryError: Java heap space |
Heap memory insufficient, objects not released | jmap -dump to get dump -> MAT to analyze large objects -> check static Map/list for continuous growth |
| GC overhead | GC overhead limit exceeded |
GC spending too much time reclaiming too little memory | Same as above; usually a memory leak or heap too small |
| Metaspace | OutOfMemoryError: Metaspace |
Too much class metadata (dynamically generated classes, ClassLoader leak) | Adjust -XX:MaxMetaspaceSize; check dynamic proxies/CGLIB; see Class Loading Troubleshooting |
| Direct buffer | OutOfMemoryError: Direct buffer memory |
NIO direct memory not released | Check ByteBuffer.allocateDirect release; adjust -XX:MaxDirectMemorySize |
| StackOverflow | StackOverflowError |
Recursion too deep or stack frame too large | Check recursion termination condition; adjust -Xss to increase stack (symptomatic, not root fix) |
| unable to create thread | OutOfMemoryError: unable to create new native thread |
Thread count reached OS limit | jstack to see thread count; check thread pool leak; adjust ulimit -u |
Stop the bleeding: First enable -XX:+HeapDumpOnOutOfMemoryError to get a dump, then restart. Root fix: Use MAT to analyze the dump, find the GC Root chain, and locate the leak point.
CPU 100% Troubleshooting
Diagnostic workflow (4-step identification):
topto find the process PID with high CPU usagetop -Hp <pid>to find the thread TID with high CPU usage (decimal)printf "%x\n" <tid>to convert to hexadecimaljstack <pid> | grep <hexadecimal tid> -A 30to see what the thread is doing
Common root causes:
| Root Cause | Manifestation | Countermeasure |
|---|---|---|
| Infinite loop / infinite recursion | jstack stack frames repeating or fixed | Fix code logic, add termination condition |
| Regex catastrophic backtracking | Stack in Pattern.matcher |
Simplify regex, or use pre-compiled + timeout |
| Large nested collection iteration | Stack in for / Stream |
Change algorithm, reduce O(n^2) to O(n) |
| Serializing large objects | Stack in ObjectOutputStream |
Limit object size, or use streaming processing |
| GC threads consuming all CPU | Multiple GC task threads with high CPU |
Actually a memory issue, see High-Frequency GC Troubleshooting |
| Intensive crypto hashing | Stack in MessageDigest |
Use a faster algorithm, or execute asynchronously |
Note: If CPU is high but business logic is not complex, first check whether GC threads are consuming all CPU (check GC logs).
High-Frequency GC Troubleshooting
Diagnostic workflow:
- Enable GC logs:
-Xlog:gc*:file=gc.log:time,uptime:filecount=10,filesize=10M(JDK9+) - Analyze logs with GCViewer or gceasy.io
- Check key metrics:
- Full GC frequency: Normal is once every few hours; once every few minutes is problematic
- GC throughput: GC time ratio should be < 5%; > 10% requires investigation
- Old generation occupancy: Continuously growing without dropping = memory leak
jstat -gcutil <pid> 1000to observe occupancy changes across generations
Common root causes and countermeasures:
| Root Cause | Countermeasure |
|---|---|
| Heap too small | Increase -Xmx |
| Object creation too fast | Caching, object pool, StringBuilder |
| Memory leak | Get dump to find leak point (see OOM Troubleshooting) |
| Large objects promoted directly to old generation | Adjust -XX:PretenureSizeThreshold |
| Metaspace insufficient | Adjust -XX:MaxMetaspaceSize (see OOM Metaspace) |
System.gc() being called |
Add -XX:+DisableExplicitGC to disable |
GC collector selection and production parameter configuration: Proactive configuration belongs to the performance-tuning skill, see ../performance-tuning/references/gc_tuning.md. This skill only covers reactive troubleshooting.
Performance Troubleshooting
When response is slow but CPU is not high and GC is normal:
jstack <pid> > thread.txtto see thread state distribution- Count
BLOCKED/WAITINGthreads:grep "java.lang.Thread.State" thread.txt | sort | uniq -c - Many
BLOCKED-> lock contention, see Thread Deadlock Troubleshooting - Many
WAITING-> thread pool idle or waiting for external resources (DB/HTTP) - Check if threads are stuck in
SocketRead-> RPC timeout or slow peer jstat -gcutil <pid> 1000to rule out GC issues
Thread Deadlock Troubleshooting
Diagnosis: jstack <pid> | grep -A 20 "Found Java deadlock" -- jstack automatically detects synchronized deadlocks and prints them.
synchronized deadlock:
- Manifestation:
jstackreports "Found 1 deadlock", two threads waiting for each other's lock - Root cause: Nested
synchronizedblocks with inconsistent lock ordering - Fix: Unify lock ordering, or use
tryLock(timeout)
Lock deadlock (ReentrantLock):
- Manifestation: Thread
WAITINGinAbstractQueuedSynchronizer, but jstack does not report deadlock (Lock deadlocks are not automatically detected) - Diagnosis: Check
jstackstack for threads stuck inlock()calls - Fix: Use
tryLock(timeout, unit)instead oflock(), release and retry on timeout
Thread leak (pseudo-deadlock):
- Manifestation: Thread count keeps growing, eventually
unable to create thread - Diagnosis:
jstack <pid> | grep "java.lang.Thread.State" | wc -lto count total threads - Fix: Check if thread pool is leaking (
new Threadnot in a pool,submitwithoutshutdown)
Class Loading Troubleshooting
ClassNotFoundException / NoClassDefFoundError / LinkageError:
jmap -clstats <pid>to see ClassLoader count and loaded class count- Abnormally many ClassLoaders -> ClassLoader leak (common in hot deployment, dynamic proxies)
- Class not found -> check classpath:
jcmd <pid> VM.classloader_stats - Same class with multiple version conflicts ->
jmap -clstatsto see if multiple ClassLoaders load the same class
Metaspace leak:
- Symptom: Metaspace does not drop after Full GC, eventually OOM Metaspace
- Diagnosis:
jmap -clstats <pid>to see if ClassLoader count keeps growing - Root cause: Dynamic proxies (CGLIB/ByteBuddy) generating new classes each time without caching, or web container hot deployment not cleaning up old ClassLoaders
- Fix: Cache proxy classes, fix hot deployment leak, adjust
-XX:MaxMetaspaceSize
Module System Troubleshooting
Failures specific to JDK 9+ module system (JPMS). Guaranteed to encounter on JDK 17.
InaccessibleObjectException
Symptom: Reflection call reports java.lang.reflect.InaccessibleObjectException: Unable to make field accessible: module java.base does not "opens java.lang" to unnamed module
Root cause: JDK 9+ module system protects non-exported packages; reflection access requires explicit opening.
Troubleshooting:
- Check which line of the error mentions "module X does not opens Y"
- Add the corresponding
--add-opensfor that module
Common --add-opens quick reference:
| Scenario | Error Module | Parameter |
|---|---|---|
Reflective access to String internals |
java.base/java.lang |
--add-opens java.base/java.lang=ALL-UNNAMED |
| Reflective access to collection internals | java.base/java.util |
--add-opens java.base/java.util=ALL-UNNAMED |
| Netty NIO access | java.base/java.nio |
--add-opens java.base/java.nio=ALL-UNNAMED |
Reflective access to Method |
java.base/java.lang.reflect |
--add-opens java.base/java.lang.reflect=ALL-UNNAMED |
| Kryo / Gson serialization | java.base/java.lang etc. |
Usually requires multiple --add-opens |
| Spring / Hibernate reflection | Multiple | See framework documentation |
Configuration location:
- Command line:
java --add-opens ... -jar app.jar - MANIFEST.MF:
Add-Opens: java.base/java.lang java.base/java.util - K8s:
JAVA_OPTSenvironment variable
--add-opens vs --add-exports
| Parameter | Purpose |
|---|---|
--add-opens |
Reflective access (open at runtime) |
--add-exports |
Compile-time + runtime access (non-reflective) |
--add-reads |
Let one module read another module |
Choose: Use --add-opens for reflection scenarios; use --add-exports for direct imports.
Module Conflict
Symptom: Startup reports Module resolution failed or module not found.
Troubleshooting:
- Check if there are multiple versions of the same module
- Check module-path (
--module-path) vs classpath - Use
java --list-modulesto see loaded modules
Module System Diagnostic Commands
# View module system parameters
jcmd <pid> VM.system_properties | grep jdk.module
# View loaded modules
java --list-modules
# Startup debugging
java --show-module-resolution -jar app.jar
Container Troubleshooting
JVM failures specific to K8s / Docker containers. For detailed troubleshooting see references/container_troubleshooting.md.
Quick reference:
| Symptom | Root Cause | Countermeasure |
|---|---|---|
Pod OOMKilled exit 137 |
cgroup memory < JVM usage | Use MaxRAMPercentage instead of fixed -Xmx |
jps cannot see JVM |
Container main process is PID 1 | Use jcmd 1 ... |
jcmd command not found |
JRE-only image | Sidecar with JDK / use jattach / use JFR |
Could not reserve enough space |
-Xmx exceeds cgroup |
Use MaxRAMPercentage=75.0 |
unable to create new native thread |
Container ulimit tightened | Adjust ulimit -u or reduce thread count |
| JFR / dump lost on disk | Container path not mounted | Mount PVC to HeapDumpPath |
Startup Failure Troubleshooting
JVM exits immediately on startup; check the error keyword:
| Error | Root Cause | Countermeasure |
|---|---|---|
Could not reserve enough space for object heap |
-Xmx exceeds physical memory |
Reduce -Xmx, or check if other processes are using memory |
Invalid initial heap size |
-Xms / -Xmx parameter format error |
Check parameter units (4g not 4096) |
Incompatible version |
Compile-time JDK version > runtime | Run with same or higher JDK |
Unsupported major.minor version |
Class file version mismatch | Use javap -verbose X.class to check major version |
Unable to open jar |
Jar file corrupted or wrong path | Check -jar path and jar integrity |
Error: LinkageError |
Class conflict | Check classpath for duplicate classes |
Command Quick Reference
JVM diagnostic commands organized by scenario:
# === Process Overview ===
jps -lvm # List JVM processes
jcmd <pid> VM.flags # View JVM startup parameters
jcmd <pid> VM.system_properties # View system properties
# === Heap Analysis ===
jmap -heap <pid> # Heap overview (generation occupancy)
jmap -histo:live <pid> | head -20 # Large object histogram (top 20)
jmap -dump:format=b,file=heap.hprof <pid> # Export heap dump
jmap -finalizer_info <pid> # View finalizer queue
# === Thread Analysis ===
jstack <pid> > thread.txt # Thread stack
jstack -l <pid> # Thread stack with lock info
top -Hp <pid> # Thread-level CPU usage
# === GC Analysis ===
jstat -gcutil <pid> 1000 # Generation occupancy + GC count (refresh every second)
jstat -gccause <pid> 1000 # Last GC cause
jcmd <pid> GC.heap_info # Heap info
# === Class Analysis ===
jmap -clstats <pid> # ClassLoader statistics
jcmd <pid> VM.classloader_stats # ClassLoader status
jmap -permstat <pid> # Permanent generation statistics (JDK7)
# === System Level ===
top # Process CPU/memory
iostat -x 1 # Disk IO
netstat -anp | grep <pid> # Network connections
JVM Parameters
Monitoring parameters commonly enabled during troubleshooting:
-XX:+HeapDumpOnOutOfMemoryError # Auto dump on OOM (essential for OOM troubleshooting)
-XX:HeapDumpPath=/var/log/dumps/ # Dump path
-XX:ErrorFile=/var/log/hs_err_%p.log # Fatal error log
-Xlog:gc*:file=gc.log:time,uptime # GC log
-XX:+PrintFlagsFinal # Print all JVM parameter final values
Proactive tuning parameters (heap/GC collector/JIT configuration): Belongs to the performance-tuning skill, see ../performance-tuning/references/jvm_params.md.
Usage
- Locate by symptom: First check the "Failure Category Quick Reference" table, jump to the corresponding section.
- Execute diagnostic workflow: Each section has numbered steps; execute in order.
- Check root cause table: After diagnosis, find countermeasures in the "Common Root Causes" table.
- Command details: When command output is unclear, Read
references/diagnostic_commands.md. - GC troubleshooting: When investigating GC issues, Read
references/gc_tuning_guide.md(reactive diagnostic perspective). For proactive selection and configuration, see performance-tuning. - Container troubleshooting: For K8s/Docker container failures, Read
references/container_troubleshooting.md. - JFR analysis: When using JFR for troubleshooting, Read
references/jfr_analysis.md. - Troubleshooting cases: When an end-to-end reference is needed, Read
references/troubleshooting_cases.md. - Do not fabricate when uncertain: JVM behavior is governed by source code and official documentation; this skill does not replace formal documentation.
Reference Entries
- Command details:
references/diagnostic_commands.md(output field meanings and examples for each JVM command) - GC troubleshooting guide:
references/gc_tuning_guide.md(GC problem troubleshooting from a reactive diagnostic perspective) - Container troubleshooting:
references/container_troubleshooting.md(JVM failures inside K8s/Docker containers: OOM killed / PID 1 / JRE-only) - JFR post-incident analysis:
references/jfr_analysis.md(JFR event quick reference, online streaming, JMC flame graphs) - Troubleshooting cases:
references/troubleshooting_cases.md(4 end-to-end cases: heap leak / CPU 100% / deadlock / Metaspace leak) - Proactive performance tuning:
../performance-tuning/SKILL.md(JVM parameter selection, GC collector selection, JIT tuning, JMH benchmarking) - Official documentation:
https://docs.oracle.com/en/java/javase/17/docs/specs/man/(JDK 17 tools) - GC log analysis:
https://gceasy.io/ - Heap dump analysis: MAT (
https://eclipse.org/mat/) - Project coding standards:
../coding-standard/SKILL.md