Threads & CPU

High CPU in a Java process: finding the thread and the code responsible

Threads & CPU · Published 2026-10-22

Close-up of the thin metal fins of a CPU heatsink
A thread burning CPU shows up first as heat and fan noise. Photo: Picdrome Public Domain Pictures, Flickr, CC0.

A Java process at several hundred percent CPU is a common production alarm. The fastest route to the cause uses a simple fact: every Java platform thread is an operating system thread, so the OS can tell you which thread is busy, and a thread dump can tell you what that thread is doing.

Step 1: is it application code, GC or the JIT?

Before chasing application threads, rule out the two JVM-internal consumers:

The per-thread view in step 2 shows these cases too: GC threads and compiler threads have recognisable names in the dump (names containing GC, G1, C1 CompilerThread, C2 CompilerThread and so on, depending on the collector and release).

Step 2: find the busy OS threads

On Linux, top can show threads instead of processes with -H, restricted to one process with -p:

$ top -H -p <pid>

The PID column now contains thread IDs. Note the two or three at the top. For a scriptable snapshot, ps can list threads with their CPU share:

$ ps -L -o tid,pcpu -p <pid> | sort -k2 -nr | head

Step 3: convert the thread ID to hexadecimal

Thread dumps print the native thread ID as nid=0x… in hexadecimal. Convert the decimal ID from top:

$ printf '%x\n' 8011
1f4b

Step 4: take a dump and match the nid

Immediately afterwards, take a thread dump and search for that nid:

$ jcmd <pid> Thread.print -l > dump.txt
$ grep -A 20 'nid=0x1f4b ' dump.txt

The stack under that thread is what it was executing at the moment of the dump. The top frames are where the CPU is going. Repeat steps 2 to 4 two or three times: a hot loop shows the same frames each time, while a thread that just happened to be busy will not.

A shortcut: the cpu= field

Recent JDKs print cpu= (CPU time consumed) and elapsed= on each thread's header line in Thread.print output. Two dumps taken a few seconds apart give a per-thread CPU delta without using top at all, which is handy in containers that lack procps tools. A small script that parses thread names and cpu= values from both dumps and sorts by difference does the job.

From inside the application

ThreadMXBean exposes the same data programmatically through getThreadCpuTime(id), provided isThreadCpuTimeSupported() returns true. Some services expose a diagnostic endpoint that returns the top threads by CPU delta together with their stacks.

Step 5: profile if the stack is not obvious

A thread dump is a single sample. When the hot thread's stack differs from dump to dump, or when CPU is spread over many threads, you need a profile: many samples aggregated.

JDK Flight Recorder takes periodic execution samples of running threads (the jdk.ExecutionSample event) and is designed for production use. Start a recording on the running process:

$ jcmd <pid> JFR.start name=cpu settings=profile duration=2m filename=/var/tmp/cpu.jfr
# after two minutes, inspect hot methods
$ jfr print --events jdk.ExecutionSample /var/tmp/cpu.jfr | head -60

JDK Mission Control's Method Profiling page, or a flame graph view, aggregates these samples into the methods with the most CPU. See Java Flight Recorder and JDK Mission Control.

Common causes, by what the stack shows

Top frames showLikely causeNext step
java.util.regex.Pattern classes, deep recursionCatastrophic backtracking on a pathological inputFind the pattern and the input; rewrite with possessive quantifiers or bounded repetition
HashMap or TreeMap internals in every sampleConcurrent modification of a non-thread-safe map, which can corrupt its structure, or a map that has grown far beyond expectationsUse ConcurrentHashMap or proper locking
Serialisation (JSON, XML) of huge objectsAn unexpectedly large payload or a reference cycleLog payload sizes; cap response size
A spin or retry loop calling Thread.onSpinWait or nothing at allBusy-waiting on a conditionBlock with a lock, condition or queue instead
Logging frames: formatting, appendersDebug logging enabled in production, or an exception logged in a loopCheck log levels and log volume
GC worker threads dominateHeap pressureGC log; heap sizing; look for a leak

In containers

Inside a container, top and ps may be missing, and the CPU limit changes the picture: a process limited to two CPUs that is throttled will appear slow without ever showing high CPU on the host. Check the container's throttling statistics as well as thread CPU. The JVM sizes its GC and compiler thread counts from the processors it detects; see the JVM in containers for how that detection works and how to override it with -XX:ActiveProcessorCount.

Checklist

Sources