Java Flight Recorder (JFR) provides continuous, low-overhead profiling by capturing runtime execution samples (jdk.ExecutionSample) directly within the JVM. In this blog, we will examine CPU Spike, a performance issue commonly caused by one or more threads becoming stuck in continuous, CPU-bound execution, whether through an infinite loop, runaway computation, or an accumulating set of active threads that never terminate. We will simulate the problem, capture the relevant JFR data, and analyze the recording to identify the underlying issue. Let’s take a closer look.
What is CPU Spike?
A CPU spike is essentially a sudden, massive jump in processor utilization, often pinning it between 80% and 100%. This happens when one or more threads aggressively hog the CPU without yielding. Instead of pausing for things like I/O, sleep, or a lock, the thread gets stuck in a RUNNABLE state, endlessly looping through the exact same code path. It’s completely different from a temporary burst triggered by a single request. When a spike is sustained, the JVM is forced to feed processing power to that lone piece of code over and over, completely starving everything else on the system.
What causes ‘CPU Spike’?
Let’s look at the list of causes for this CPU Spike issue:
- Infinite Looping Threads: When a thread loops forever in code the CPU will start to spike. For example, a while loop that never stops will keep running and using more CPU on the machine.
- Repeated Full GCs: When the JVM keeps running Garbage Collection, CPU usage will increase. And when this happens the application has a memory leak and it creates objects without deleting them. The JVM tries to free up memory but it can’t. So it tries again and again, which causes the CPU spike.
- Intensive Computation: Heavy tasks or algorithms that need a lot of processing power can cause CPU spikes. Making sure algorithms are efficient or spreading work across threads can actually help.
- Inefficient Code: Badly written code can cause CPU Spike. You could use certain tools to identify what lines of code is actually causing the said issue.
- Thread Contentions: When threads fight for locks or resources CPU usage goes up. Using locks and synchronization mechanisms carefully is important.
Simulating CPU Spike Performance Issue
To understand how CPU Spike appears in JFR data, let’s reproduce the issue using a sample Java application.
The following program deliberately launches multiple threads in succession, each running an empty loop that never exits, to drive sustained high CPU utilization.
package com.buggyapp.cpuspike;import com.buggyapp.util.StringUtil;public class CPUSpikeDemo { public static final String NUMBER_OF_CPU_CYCLES = "buggyApp.CPUCycles"; public static void start() throws InterruptedException { int noOfCycles = 6; if (StringUtil.isValid(System.getProperty(NUMBER_OF_CPU_CYCLES))) { try { noOfCycles = Integer.parseInt(System.getProperty(NUMBER_OF_CPU_CYCLES)); } catch (NumberFormatException e) { System.out.println("Failed to parse buggyApp.CPUCycles"); } } int counter = 0; while (counter < noOfCycles) { new CPUSpikerThread().start(); Thread.sleep((2 * 60 * 1000)); counter++; } System.out.println(noOfCycles + " threads launched!"); } public static void stop() { new CPUSpikerThread().stop(); System.out.println("CPU spike terminated!"); }}
In this program, the start() method launches a new CPUSpikerThread every 2 minutes, up to a configurable number of cycles (noOfCycles, which defaults to 6). Each CPUSpikerThread runs a while (flag) loop that calls a method doing no actual work, so the thread just checks the same condition over and over, as fast as the CPU allows. As the application continues to run, each newly launched thread adds another such busy-waiting thread to the mix, so overall CPU utilization climbs in steps every two minutes until the system is under sustained, heavy CPU pressure, visible as a CPU spike that doesn’t resolve on its own.
Capturing JFR Data for Troubleshooting CPU Spike
To capture JFR data for troubleshooting the CPU Spike issue, we recommend that you follow the steps below:
If you are interested in learning about the other methods available to capture JFR recording, we recommend that you read ‘How to Capture Java Flight Recorder (JFR)?’ blog.
Step 1: Start a JFR recording against the running JVM:
jcmd {PID} JFR.start \ name=loadTestCapture \ settings=profile \ filename=/tmp/tomcat.jfr
Note: Replace <PID> with the Process ID (PID) of your Java application running inside the container. If you don’t know how to find it, refer to our guide on finding the Java application Process ID (PID) for step-by-step instructions.
Step 2: Let it run while the CPU spike is occurring, then stop it manually:
jcmd {PID} JFR.stop \ name=loadTestCapture
Step 3: Alternatively, for Fixed-Duration Capture, you can start a recording that automatically exits after a predefined duration (for example, 15 minutes) in a single step:
jcmd {PID} JFR.start \ name=loadTestCapture \ settings=profile \ duration=15m \ filename=/tmp/tomcat.jfr
Analyzing CPU Spike Using the JFR Data
You can analyze JFR recording using yCrash JFRPlayer by following the steps mentioned below.
Step 1: Upload the jfr file to your yCrash JFRPlayer.
Note: yCrash JFRPlayer is available in both cloud and on-premises versions. Use one of the options below to get started:
Cloud service: Register and upload your JFR file online.
On-Premises: Install and run yCrash JFRPlayer on your local machine or within your organization’s environment.
Fig: Uploading a standalone JFR file to yCrash JFRPlayer
Step 2: I’d recommend you review the AI overview section first, as it gives you an executive summary of the issue in plain language, along with the Root Cause Analysis (RCA) that traces the issue back to the specific method and class responsible, in our case, pointing to CPUSpikerThread.
Fig: yCrash JFRPlayer AI Overview identifying CPUSpikerThread.run() as the primary cause of the CPU spike
Step 3: Next is to check the “Issues in the Application” section, which flags every problem individually. In this case, this includes a hot method detection: com.buggyapp.cpuspike.CPUSpikerThread.run() is consuming the majority of CPU, along with the line of code the thread is looping on.
Fig: yCrash JFRPlayer flagging the hot method and looping thread
Step 4: Click into the flagged thread’s stack trace (linked directly from the issue) to confirm the exact execution path and verify it matches the reported line number in your source code.
Fig: Stacktrace confirming the JFR periodic thread looping on emitEvent()
Step 5: Cross-check the “Issues in the Device” section, which reports system-level confirmation (e.g., overall process CPU usage exceeding a threshold), to confirm the application-level finding aligns with what the OS is also observing.
Fig: Device-level confirmation showing process CPU usage exceeding 80%
Simple, right? Now that we have analyzed the data, we are equipped with all the information to fix the problem.
If you face any challenges while analyzing a JFR file using yCrash JFRPlayer, check out our FAQ for answers to common questions and troubleshooting guidance.
Root Cause & Resolution
Now let’s try to understand what caused it and how we go about fixing it.
What caused it:
The CPUSpikerThread class runs a loop that never stops. Inside the loop, it calls a method that does nothing, so the thread just keeps checking the same condition over and over, as fast as the CPU allows. On top of that, the program launches a new one of these threads every two minutes and never stops the earlier ones. So instead of one thread maxing out the CPU right away, the load builds up gradually as more threads pile on.
Both the JFR recording and the yCrash JFRPlayer report point to the same spot: the run() method in CPUSpikerThread. It’s the thread doing the looping, and it’s what’s driving the CPU usage up.
How to fix it:
- Give the loop a way to stop. Right now, nothing ever tells it to end.
- Don’t spin up threads by hand without a plan for shutting them down. A thread pool with a fixed size is a safer way to manage this, since it lets you control how many threads run and stop them cleanly when you’re done.
- If a thread needs to check something repeatedly, have it pause briefly between checks instead of checking constantly. Even a short pause frees up the CPU for other work.
- After making the fix, record another JFR session and confirm the hot method is gone and CPU usage has settled back down.
Conclusion
Diagnosing and identifying the root cause of a CPU Spike can be challenging, especially in complex production environments where multiple symptoms often overlap. In this example, the JFR recording was analyzed using yCrash JFRPlayer, which automatically identified the key performance bottlenecks and correlated JVM events to pinpoint the underlying problem. The analysis traced the issue to the run() method in CPUSpikerThread. By examining the relevant JFR insights, including execution samples, thread activity, memory allocations, garbage collection events, lock contention, and I/O behaviour (as applicable), we were able to identify the root cause and determine the appropriate fix.

