Java Diagnostics
GitHubOfficialFirst committed by jduboisDistributed by GitHub
- Skill
- Guidelines
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured, safety-conscious approach to diagnosing common JVM performance issues. It categorizes diagnostic tools into three permission tiers based on their intrusiveness and potential impact on production systems. By following a symptom-first decision table, users can systematically identify and resolve issues such as high CPU usage, memory leaks, thread starvation, and GC pauses. The skill emphasizes safe data collection, user consent for sensitive operations, and local analysis of diagnostic artifacts.
What it does
- Provides a decision table mapping common JVM symptoms to specific diagnostic tools.
- Defines three tiers of diagnostic operations based on safety and intrusiveness.
- Offers guidance on using JDK tools like jcmd, jstack, jmap, and JFR.
- Includes instructions for profiling with async-profiler and analyzing native memory.
- Outlines best practices for diagnosing JVM issues within containerized environments.
- Establishes a workflow for capturing, analyzing, and cleaning up diagnostic data.
When to use it
- When a Java application experiences high CPU, memory leaks, or OutOfMemoryErrors.
- When investigating slow startup times or unexplained latency in requests.
- When diagnosing thread-related issues like deadlocks or thread starvation.
- When analyzing long GC pauses or native memory growth.
- When a JVM crashes or hangs and requires post-mortem analysis.
Advantages
- Promotes safe diagnostic practices by enforcing permission tiers.
- Reduces trial-and-error by providing a symptom-based diagnostic path.
- Minimizes production impact by prioritizing non-intrusive tools first.
- Provides clear guidance on handling sensitive data in heap dumps.
- Standardizes the diagnostic process across different JVM environments.
Usage examples
- “My Java application is experiencing high CPU usage. How should I start diagnosing it?”
- The agent will guide you to capture thread dumps using Tier 1 tools and map them to native thread IDs, followed by using JFR or async-profiler for a CPU flame graph.
- “I suspect a memory leak in my production JVM. What is the safe way to investigate?”
- The agent will instruct you to use Tier 2 tools like jstat and class histograms first, and only proceed to Tier 3 heap dumps with explicit consent and proper precautions.
- “How do I diagnose native memory growth in a containerized Java app?”
- The agent will suggest comparing RSS versus heap usage, enabling Native Memory Tracking (NMT), and using async-profiler's nativemem mode.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/github/java-diagnostics/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip java-diagnostics.zip -d .agents/skills
Documentation by jdubois
Java Diagnostics
Pick the lightest tool that answers the question, starting from the symptom.
Permission tiers (obey them)
| Tier | Tools | Rule |
|---|---|---|
| 1. Read-only | jps -l, jcmd <pid> help/VM.version/VM.flags/Thread.print, jstat, jstack, reading logs | May run after stating the exact command. Class histograms (jcmd <pid> GC.class_histogram, jmap -histo, jmap -histo:live) are NOT Tier 1: they run heap inspection at a safepoint and can pause application threads on a large heap, so treat them as Tier 2 (consent, stated overhead). The default forms and -histo:live additionally force a full GC; -all=true / jmap -histo skip the full GC but still pause during the scan (and count unreachable objects). |
| 2. Recording / attach | JFR (JFR.start, -XX:StartFlightRecording), async-profiler, NMT (-XX:NativeMemoryTracking), enabling GC logs | Requires user consent. State duration and expected overhead (JFR default profile is about 1-2%; async-profiler depends on mode; NMT summary roughly 5-10%, detail more) before running. |
| 3. Intrusive / sensitive | Heap dumps (GC.heap_dump, jmap -dump), jhsdb, commands that print JVM arguments or system properties (jps -v/-lv/-m, bare jcmd, VM.system_properties, VM.command_line, jinfo, ps aux on a Java process; they can expose secrets, redact output), JDWP with suspend, anything on production | Requires explicit consent, a stated target path, free-disk check (a dump is about the size of the used heap), and a stated pause. Heap dumps contain secrets (passwords, tokens, personal data). Never share or upload them; redaction is NOT a reliable safeguard. Analyze locally and delete afterwards. |
Never run Tier 2 or Tier 3 commands against production unless the user explicitly instructs it. Run diagnostic tools as the same OS user as the target JVM. Prefer reproducing on dev or staging.
Decision table
| Symptom | First step | Then | Reference |
|---|---|---|---|
| High CPU | Thread dumps x3 (Tier 1) + top -H to map hot native thread ids to nid | JFR or async-profiler CPU flame graph (Tier 2) | threads, jfr, async-profiler |
| Memory leak / OOM | jstat -gcutil, GC.class_histogram -all=true twice, minutes apart (Tier 2: safepoint pause even with -all=true; without it a full GC too; get consent on production) | Heap dump + Eclipse MAT (Tier 3) or JFR OldObjectSample (Tier 2) | heap, gc |
| Slow startup | -Xlog:class+load, framework startup/condition reports, timestamped logs | JFR from start, async-profiler wall-clock | jfr, async-profiler |
| Deadlock / thread starvation | jcmd <pid> Thread.print (reports deadlocks), compare dumps over time | Virtual threads: Thread.dump_to_file | threads |
| GC pauses | GC log (-Xlog:gc*) and jstat -gc | JFR GC events; tune collector/heap | gc |
| Native memory growth (RSS grows, heap does not) | Compare RSS vs heap; enable NMT, jcmd VM.native_memory baseline / summary.diff (Tier 2) | async-profiler nativemem or allocator tools | nmt, containers |
| Slow requests | Tracing/metrics and logs first; Actuator or Dev UI (see below) | JFR (socket/file/lock events), async-profiler wall-clock | jfr, async-profiler |
| JVM crash / hang, no tools respond | hs_err_pid*.log, core dump | jhsdb (Tier 3) | basics |
Start with jps/jcmd basics to identify the target JVM and confirm which JDK version is running.
Workflow
Capture the symptom precisely (when, how often, since which change, JDK version, container limits).
Choose the tier needed; state the command, its overhead, and output location, and get consent for Tier 2/3.
Collect data to a dedicated directory, with timestamps. Prefer several samples over one.
Form a hypothesis from the data, then confirm with a second source (for example a thread dump plus a flame graph).
Fix, re-measure with the same method, and clean up artifacts (dumps, recordings).
Application-level insights (optional)
JDK tools see the JVM; some questions (which endpoint, which SQL query, which bean) are easier at the application level. Optional options, in order of preference:
Spring Boot Actuator (
metrics,health,loggers,httpexchanges) and Quarkus Dev UI / Micrometer metrics are built in and need no extra tool. Expose Actuator endpoints minimally; Forheapdumpandthreaddump, follow the policy in thespring-observabilityskill: never expose them over public HTTP; the only exception is a temporary, user-consented window with authentication, TLS and network restriction, removed afterwards.BootUI (https://github.com/jdubois/boot-ui , https://www.julien-dubois.com/boot-ui/): a local-only developer console with an opt-in MCP server for Spring Boot and Quarkus. Optional. Its skill can be installed with
gh skill install jdubois/boot-ui skills/bootui.
If considering BootUI:
Ask the user for consent before adding any dependency or installing the skill.
Use it in a local/dev profile only; never expose it on a network, and never enable its heap dump, thread dump endpoints or MCP server in production.
Check the capability and version first (read its README for the supported Spring Boot / Quarkus versions) instead of assuming features.
Follow the project's own documentation for usage; this skill does not reproduce it.
Containers
See containers for attaching tools inside containers, -XX:MaxRAMPercentage, and cgroup memory limits.
References
Debugger-based investigation: the
java-debuggingskill.
Official docs (prefer them over this skill for the JDK in use; change 21 to your version): jcmd https://docs.oracle.com/en/java/javase/21/docs/specs/man/jcmd.html, jfr https://docs.oracle.com/en/java/javase/21/docs/specs/man/jfr.html, JFR guide https://docs.oracle.com/en/java/javase/21/jfr/introduction-jdk-flight-recorder.html, troubleshooting guide https://docs.oracle.com/en/java/javase/21/troubleshoot/, async-profiler releases https://github.com/async-profiler/async-profiler/releases.
Files
- ATTRIBUTION.mdAdded by AutoGineer863 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md7.2 KiB
- references/async-profiler.md3.0 KiB
- references/containers.md1.7 KiB
- references/gc.md1.6 KiB
- references/heap.md3.0 KiB
- references/jcmd-basics.md1.9 KiB
- references/jfr.md2.3 KiB
- references/native-memory.md1.5 KiB
- references/thread-dumps.md3.3 KiB
references/thread-dumps.md
Thread dumps and deadlocks (Tier 1)
jcmd <pid> Thread.print # preferred; includes deadlock report
jcmd <pid> Thread.print -l # also lists ownable synchronizers/locks
jstack <pid> # equivalent older toolDo not use kill -3 <pid> / SIGQUIT as an unconditional fallback. On Unix, use it only after verifying that the target JVM has its thread-dump SIGQUIT handler installed and that stdout/stderr are captured. Explicitly exclude JVMs started with -Xrs / -XX:+ReduceSignalUsage, or with unknown/custom signal handling: without the JVM handler, SIGQUIT can terminate the process and produce a core dump. If signal handling cannot be verified, do not send the signal; resolve the jcmd/attach permissions instead. See the JDK launcher -Xrs documentation and Linux signal defaults.
Take 3 dumps, 5-10 seconds apart, to see what changes. A single dump is a snapshot, not a trend. Thread dumps include stack frames and thread names, which may contain request data; do not share publicly.
High CPU: map hot threads
top -H -p <pid> # find hot native thread ids (decimal)
printf '%x\n' <tid> # convert to hex, match "nid=0x..." in the dumpCheck whether the hot thread is application code, GC threads (GC Thread, G1 Conc), or JIT compiler threads (C2 CompilerThread).
Deadlock
Thread.print ends with "Found one Java-level deadlock" and lists the lock cycle. Otherwise look for many threads BLOCKED on the same monitor (waiting to lock <0x...>) whose owner is itself waiting. Thread starvation shows pools with all threads WAITING/TIMED_WAITING in the same call (e.g., a connection pool getConnection, a blocking HTTP call) while the queue grows.
Virtual threads (JDK 21+)
Thread.print does not list virtual threads. Use:
jcmd <pid> Thread.dump_to_file -format=json threads.json
jcmd <pid> Thread.dump_to_file threads.txtThread.print shows only platform threads (virtual threads were verified missing on JDK 27, present in Thread.dump_to_file). Pinned virtual threads (blocked inside synchronized on JDK 21-23, or native frames on any version) can be investigated with the JFR event jdk.VirtualThreadPinned (see jfr). -Djdk.tracePinnedThreads=full (or short, which prints only the frames holding monitors) works only on JDK 21-23: on 21.0.12 it printed VirtualThread[#20,vt-pinned]/runnable@ForkJoinPool-1-worker-1 reason:MONITOR with the stack and <== monitors:1. JEP 491 removed it in JDK 24 (verified silent on 27: no output). From JDK 24, synchronized no longer pins; pinning remains for native frames and class initializers (a virtual thread sleeping inside a static initializer was reported by JFR on JDK 27 with pinnedReason = "VM call to Pin$Slow.<clinit> on stack"). On JDK 21-23 the same JFR event also works (verified on 21.0.12 for the synchronized case).
Reading tips
States:
RUNNABLE(on CPU or in native I/O),BLOCKED(monitor),WAITING/TIMED_WAITING(park, sleep, wait).RUNNABLEinsocketRead0is usually waiting on the network, not using CPU.Group threads by identical top frames to find the dominating pattern.
Requirements and compatibility
Written for: GitHub Copilot
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs an MCP server
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- First committed by
jdubois
- Publisher
- GitHub (Official)
- Distributed by
- GitHub
- Source
- github/awesome-copilot · skills/java-diagnostics · commit 797337f
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
- Contributors
- jdubois
AutoGineer is not affiliated with or endorsed by jdubois or GitHub.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download java-diagnostics.zip