跪拜 Guibai
← All articles
Backend

A 2 AM CPU 100% Alert That Ended at a GC Storm: A Three-Layer Java Diagnosis Runbook

By Bazingga ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Java teams still running CMS (common on many JDK 8 deployments) can't afford to guess whether a CPU spike is business code or collector behavior; this runbook turns a 2 AM panic into a repeatable checklist. It also exposes the jstack socket-file permission mismatch that wastes hours, and shows why p99 spikes plus a low load average are a fingerprint of pulse GC rather than a busy loop.

Summary

The post starts with a six-step incident framework and then drills into three diagnostic layers. Monitoring side overlays instance CPU curves to decide single-machine vs cluster-wide, and reads avg vs p99 to preselect whether the cause is a GC pause or chronic slowdown. Machine side reads top's load, us/sy/wa, and thread states to establish causality, catching the telltale contradiction of instant 14-core CPU with low load average and only one running task.

The JVM layer runs the four-command chain: `top` to find the process, `top -Hp` to find the hot thread, `printf '%x'` to convert the decimal TID to hex, then `jstack` to reverse-lookup the stack. The stack reveals Gang worker threads of the CMS collector, and `jstat -gcutil` confirms it with old-gen at 88% and FGC counts rising between samples. The article also covers the jstack "Unable to open socket file" permission trap, three-shot thread dumps to filter sampling noise, and the pre-restart evidence protocol—thread stacks, GC logs, and heap dump—so a restart doesn't erase the root cause.

Takeaways
— Overlay all instance CPU curves first: if only one machine is high, the cause is local to that instance; if all are high, it's in shared code, config, or dependencies.
— When QPS stays flat but CPU rises, per-request cost increased; when QPS and CPU rise together, it's a capacity issue.
— In `top`, %CPU is an instantaneous sample; watch TIME+ growth over 10 seconds to find the truly hot thread.
— To map an OS thread ID from `top -Hp` to a JVM `nid` in jstack, convert the decimal TID to hex with `printf '%x'`.
— jstack's 'Unable to open socket file' is usually a user identity mismatch, not a hung JVM; run with `sudo -u <owner>` before resorting to `-F`.
— Take three jstack dumps 5 seconds apart and look for a call stack that appears in all three; this filters out transient sampling noise.
— jstat -gcutil shows FGC counts increasing between samples; old-gen usage persistently above ~88% plus rising FGC is the quantitative proof of a GC storm.
— CMS concurrent worker threads appear as 'Gang worker (Parallel CMS Threads)' in jstack; if these top the hot-thread list, CPU is being burned by GC, not business logic.
Conclusions

The article's evidence hierarchy—circumstantial (pulsed CPU), direct (thread name), quantitative (jstat)—is what separates a structured investigation from command layer-caking; each hypothesis is tied to a concrete next command, which is what actually shortens mean time to resolution.

The jstack socket error is a warning that operational tooling's error messages are written for the common case, not for privilege mismatches; the same binary on the same machine with a different uid produces a message that says the process is dead when it's simply a permission check.

The 'three numbers in contradiction' technique—instant %CPU, load average, and running-task count—is a cheap way to spot pulse workloads like periodic GC cycles from a single top snapshot; it deserves to be an alert rather than a manual read.

CMS's 'concurrent' branding obscures real stop-the-world windows; the article's insistence on checking Full GC log timestamp density rather than the collector's name is the practical takeaway for anyone tuning or reviewing GC behavior.

The post's advice to preserve three evidence types (thread stack, GC data, heap dump) before a restart institutionalizes the lesson that a restart without evidence is a roll of the dice in incident review.

Concepts & terms
STW (Stop The World)
A GC phase where all application threads pause at safepoints so the collector can safely mark or compact objects; the pause can last seconds during a Full GC, directly causing API latency spikes.
CMS (Concurrent Mark Sweep)
A low-pause Java garbage collector that does most marking concurrently with application threads, but its initial mark and remark phases still stop the world; on old-gen fragmentation it can fall back to Serial Old's single-threaded Full GC, causing multi-second pauses.
nid (Native thread ID)
The OS-level thread ID shown in jstack output in hexadecimal; converting the decimal TID from `top -Hp` via `printf '%x'` maps the OS-scheduled thread to its Java stack trace.
Attach Listener
A HotSpot mechanism that lets tools like jstack connect via a socket file `/tmp/.java_pid<pid>`; it uses SO_PEERCRED to verify the connecting user's uid matches the JVM's owner, so root cannot attach to another user's JVM unless run as that user.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗