How to Diagnose and Fix High CPU and Memory Usage in Java Applications
This guide walks through identifying Java processes that cause high CPU load, extracting the hottest threads with top and jstack, analyzing JVM memory regions, interpreting GC logs, and applying practical JVM tuning parameters and tools such as jmap, jstat, and MAT to resolve performance bottlenecks.
Java Application
1. High CPU Load
1.1 Analyzing the Issue
Use
topto find the Java process ID that consumes the most CPU.
Run
top -Hp <pid>to list the top CPU‑consuming threads within that process.
Export the Java stack trace with
jstack -l <pid> > /tmp/java_pid.log.
Convert the identified thread IDs to hexadecimal (e.g.,
printf "%X" thread_id).
Search the hexadecimal thread IDs in the stack log file. The following flowchart illustrates the steps:
Sometimes excessive thread creation can be the root cause:
<code># 查看该进程有多少线程
ps p 9534 -L -o pcpu,pmem,pid,tid,time,tname,cmd | wc -l</code>1.2 Solution
<code>我们把对应的线程id的日志拿给我们的开发,进行定位错误,这里容易定位出的错误是:</code>Thread is in WAITING state.
Thread is BLOCKED .
After locating the problematic code, inform the developers to verify and fix the issue.
2. Excessive Memory Usage
The JVM divides its managed memory into several regions. Some are thread‑private (Program Counter, Java Stack, Native Stack) and others are shared (Heap, Method Area, Direct Memory).
2.1 From Memory Reclamation Perspective
The heap is the main area managed by the garbage collector, often called the GC heap. Modern collectors use generational algorithms, splitting the heap into Young (Eden, From Survivor, To Survivor) and Old generations. Since JDK 1.8 the Permanent Generation has been replaced by Metaspace, which resides in native memory.
Typical allocation flow: objects are created in Eden; when Eden fills, a Minor GC moves surviving objects to Survivor spaces; if Survivor spaces fill, objects are promoted to Old Generation; when Old fills, a Full GC occurs.
Use
jmap -heap <pid>to inspect heap layout, e.g.:
<code>[root@host ~]# jmap -heap 11764
Attaching to process ID 11764, please wait...
... (output truncated for brevity)</code>Frequent Young GCs increase CPU usage; Full GCs should be avoided. Causes of excessive GCs include too small Young or Old generation sizes, explicit
System.gc()calls, or insufficient Metaspace.
Typical JVM options for tuning (adjust according to your project):
<code>-Xms2048m
-Xmx2048m
-XX:MaxHeapFreeRatio=100
-XX:MinHeapFreeRatio=0
-Xmn900m
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=256m
-XX:SurvivorRatio=7
-XX:MaxTenuringThreshold=14
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-XX:+PrintHeapAtGC</code>After tuning, a typical result is a Minor GC every 20‑30 minutes with no Full GC or Metaspace‑induced GC.
2.2 From Code Perspective
Analyze memory distribution per class, identify large objects, and detect leaks. Use
jmap -dump:live,format=b,file=/tmp/dump.dat <pid>to generate a heap dump, then analyze it with Eclipse MAT.
MAT provides views such as Histogram, Dominator Tree, Top Consumers, Duplicate Classes, Leak Suspects, and Top Components to pinpoint memory hotspots.
Other useful tools include JConsole and Java VisualVM.
Case Analysis
Example GC log shows a Minor GC followed by a Full GC triggered by Metaspace exhaustion:
<code>[GC (Metadata GC Threshold) [PSYoungGen: 282234K->101389K(523264K)] 589068K->410894K(1921536K), 0.1611903 secs]
[Full GC (Metadata GC Threshold) [PSYoungGen: 101389K->0K(523264K)] [ParOldGen: 309505K->258194K(1398272K)] [Metaspace: 268611K->268101K(1294336K)], 1.8562117 secs]
</code>The Full GC was caused solely by Metaspace reaching its limit, not by heap pressure.
To view default JVM flags, run
java -XX:+PrintFlagsInitial | grep MetaspaceSize.
Raymond Ops
Linux ops automation, cloud-native, Kubernetes, SRE, DevOps, Python, Golang and related tech discussions.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.