How to Detect and Fix JVM CPU Spikes, Deadlocks, and Memory Leaks

Learn practical JVM tuning techniques by identifying high CPU usage, diagnosing deadlocks, and uncovering memory leaks using tools such as top, jstack, jps, jstat, and jmap, with step‑by‑step commands and code examples to help you optimize Java application performance.

Programmer DD
Programmer DD
Programmer DD
How to Detect and Fix JVM CPU Spikes, Deadlocks, and Memory Leaks
Assume you already understand the runtime data areas and common garbage collection algorithms, as well as the GC collectors supported by Hotspot.

High CPU Usage

High CPU usage should be analyzed case by case. If a sudden traffic surge due to a business activity causes the CPU to spike and then drop after the activity ends, this is normal. However, if the server has limited resources (e.g., a single‑core CPU), even a modest traffic increase can exhaust CPU, and upgrading the hardware is advisable.

If the CPU remains high for a long period, it often indicates code with excessive loops or a dead loop. The investigation steps are:

(1) Use the top command to view CPU usage.

The process ID shown by top matches the VMID displayed by the jps tool.

(2) Use top -Hp <pid> to view thread‑level CPU usage.

Thread ID 7287 is continuously consuming CPU.

(3) Convert the thread ID to hexadecimal.

# printf "%x" 7287
1c77

Remember the hexadecimal value for the next step.

(4) Use jstack to inspect the thread stack.

# jstack 7268 | grep 1c77 -A 10
"http-nio-8080-exec-2" #16 daemon prio=5 os_prio=0 tid=0x00007fb66ce81000 nid=0x1c77 runnable [0x00007fb639ab9000]
   java.lang.Thread.State: RUNNABLE
   at com.spareyaya.jvm.service.EndlessLoopService.service(EndlessLoopService.java:19)
   ...

The stack trace shows the thread executing EndlessLoopService.service at line 19, confirming the infinite loop.

Deadlock

Deadlocks are less obvious because the involved threads are in a waiting state (WAITING or TIMED_WAITING) and do not consume CPU, but they block request processing, leading to timeouts.

Use jstack to detect deadlocks:

(1) Find the Java process with jps .

# jps -l
8737 sun.tools.jps.Jps
8682 jvm-0.0.1-SNAPSHOT.jar

(2) Run jstack and look for the deadlock report at the end of the output.

Java stack information for the threads listed above:
===================================================
"Thread-4":
   at com.spareyaya.jvm.service.DeadLockService.service2(DeadLockService.java:35)
   - waiting to lock <0x00000000f5035ae0> (a java.lang.Object)
   - locked <0x00000000f5035af0> (a java.lang.Object)
   ...
"Thread-3":
   at com.spareyaya.jvm.service.DeadLockService.service1(DeadLockService.java:27)
   - waiting to lock <0x00000000f5035af0> (a java.lang.Object)
   - locked <0x00000000f5035ae0> (a java.lang.Object)
   ...
Found 1 deadlock.

The output clearly shows the two threads waiting on each other's locks.

Memory Leak

Although Java has automatic garbage collection, memory leaks can still occur when objects remain reachable.

A leak manifests as gradually decreasing available memory and eventually an OutOfMemoryError. Simply increasing -Xmx does not solve the root cause.

Example program that triggers a leak:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
    public static void main(String[] args) {
        Main main = new Main();
        while (true) {
            try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            main.run();
        }
    }
    private void run() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            executorService.execute(() -> { /* do something */ });
        }
    }
}

Run with limited heap:

-Xms20m -Xmx20m -XX:+PrintGC

GC logs show continuous collections without reclaiming enough memory, eventually leading to:

java.lang.OutOfMemoryError: Java heap space

To locate the leaking objects, enable heap dumps on OOM:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap.bin

Analyze the dump with Eclipse MAT; it often reveals many Thread and ThreadPoolExecutor instances that were never shut down.

Properly shutting down thread pools (or using a singleton) prevents the leak.

Additional Diagnostic Tools

(1) Locate the process ID with jps .

C:\...\>jps -l
24836 org.example.net.Main
...

(2) Use jstat to monitor GC activity.

# jstat -gcutil -t -h8 24836 1000
Timestamp   S0   S1   E   O   M   CCS  YGC  YGCT  FGC  FGCT  GCT
29.1       32.81 0.00 23.48 85.92 92.84 84.13 14 0.339 0 0.000 0.339
...

Columns show survivor spaces, Eden, old generation, Metaspace, GC counts and times.

(3) Dump a live heap with jmap without waiting for OOM.

# jmap -dump:live,format=b,file=heap.bin 24836

Conclusion

These three scenarios—high CPU, deadlock, and memory leak—illustrate how to use JVM tools to pinpoint performance problems. Effective JVM tuning requires careful measurement and iterative adjustment rather than blind parameter changes.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaJVMperformancedeadlockmemory leakCPUProfiling
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.