分析 java 占用 cpu 过高的原因以及 java ScheduledThreadPoolExecutor bug 介绍

近期发现一个java进程的cpu占用接近100%。

开始简单地认为是由于给java分配的内存不足,从而导致频繁GC。

于是首先的处理方式就是直接给该java程序分配更多的内存,然而进程启动没几分钟,cpu占用再次接近100%,看来问题没这么简单。

 

一. 分析 java 占用 cpu 过高的原因

1. 通过top命令直接查到该java进程的进程ID,可以看到进程ID为26260。

-bash-4.2$ top
top - 10:59:04 up 40 days, 19:54,  2 users,  load average: 6.61, 5.06, 5.27
Tasks:  92 total,   1 running,  91 sleeping,   0 stopped,   0 zombie
%Cpu(s): 99.8 us,  0.2 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  8010372 total,   640004 free,  5507184 used,  1863184 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  2194908 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                                                                       
26260 root      20   0 3631872 189640  16676 S  98.3  2.4   5:34.30 java   

2. 再次通过top命令找到该进程中占用cpu较高的线程,该线程的线程ID为26275。

[root@aliyun dancen]# top -H -p 26260
top - 15:03:57 up 40 days, 23:59,  2 users,  load average: 1.08, 1.07, 1.11
Threads:  27 total,   1 running,  26 sleeping,   0 stopped,   0 zombie
%Cpu(s): 53.3 us,  0.0 sy,  0.0 ni, 46.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  8010372 total,   447420 free,  5668448 used,  1894504 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  2033320 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                                                                                                        
26275 root      20   0 3678404 326680  17116 R 99.9  4.1 248:24.00 java                                                                                                                                           
26260 root      20   0 3678404 326680  17116 S  0.0  4.1   0:00.00 java                                                                                                                                           
26261 root      20   0 3678404 326680  17116 S  0.0  4.1   0:01.54 java                                                                                                                                           
26262 root      20   0 3678404 326680  17116 S  0.0  4.1   0:00.06 java

3. 通过printf命令得出该线程ID的十六进制表示为66a3。

[root@aliyun dancen]# printf '%x\n' 26275
66a3

4. 使用jstack命令打印该java进程的线程堆栈信息,该命令需要root权限来执行。

[root@aliyun dancen]# jstack 26260 > jstack.log

5. 在jstack.log中找出本地ID,即nid为66a3的线程信息。

[root@aliyun dancen]# cat jstack.log | grep 66a3 -A 10
"pool-3-thread-1" #12 prio=5 os_prio=0 tid=0x00007f0654780000 nid=0x66a3 runnable [0x00007f063c722000]
   java.lang.Thread.State: RUNNABLE
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.poll(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1066)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:748)

"Log4j2-TF-5-Scheduled-1" #9 daemon prio=5 os_prio=0 tid=0x00007f06545de800 nid=0x66a2 waiting on condition [0x00007f06441ca000]
   java.lang.Thread.State: TIMED_WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)

可以看出问题出在ScheduledThreadPoolExecutor这个类的内部类DelayedWorkQueue的poll方法。

 

二. ScheduledThreadPoolExecutor bug 介绍

从上面可以看到,ScheduledThreadPoolExecutor这个类的内部类DelayedWorkQueue的poll方法消耗了大量的cpu资源。

查看代码中用到ScheduledThreadPoolExecutor的地方:

int corePoolSize = 0;
ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(corePoolSize);

这里声明了一个corePoolSize为0的线程池,这是有点诡异的地方,但是是允许的。

然而,问题就出在这个诡异点,这里触发了java的一个bug:

 

JDK-8129861:

ScheduledThreadPoolExecutor with corePoolSize = 0 causes 100% load on one CPU core

 

该bug即使用corePoolSize为0的ScheduledThreadPoolExecutor,会导致cpu飙升,已在java 9修复。具体描述请参见:

https://bugs.openjdk.java.net/browse/JDK-8129861

你可能感兴趣的:(linux,Java,基础)