Java应用CPU使用率高

Java应用程序运行中,会遇到Java应用(JVM)进程CPU使用率高的情况。在这种情况下,Java应用的性能通常会下降,我们可以借助一些工具或命令收集问题信息,进行分析诊断,找到并解决造成Java应用(JVM)进程CPU使用率高的原因。

重要 本文档可能包含第三方产品信息,该信息仅供参考。阿里云对第三方产品的性能、可靠性以及操作可能带来的潜在影响,不做任何暗示或其他形式的承诺。

使用edas-agent自带的命令诊断

EDAS为导入ECS集群中的ECS提供了一个可以直接显示出应用进程使用CPU的线程及其StackTrace,可以帮助您快速找到造成应用进程CPU使用率高的问题原因。

通过SSH登录到CPU高的应用进程所在的ECS,执行su - admin命令切换到admin账号后,然后执行以下命令,即可查看到应用进程中消耗CPU高的线程(默认前5个)。

edas busy-threads
说明 执行edas busy-threads -h命令可以查看该命令的具体使用帮助。

系统显示类似如下。

09/28/19 22:57:07 [INFO] EXECUTING: busy-threads
[1] Busy(4.6%) thread(3222/0xc96) stack of java process(3221) under user(admin):
"main" #1 prio=5 os_prio=0 tid=0x00002ab68004e800 nid=0xc96 runnable [0x00002ab67c1df000]
 java.lang.Thread.State: RUNNABLE
  at java.net.PlainSocketImpl.socketAccept(Native Method)
  at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)
  at java.net.ServerSocket.implAccept(ServerSocket.java:545)
  at java.net.ServerSocket.accept(ServerSocket.java:513)
  at org.apache.catalina.core.StandardServer.await(StandardServer.java:490)
  at org.apache.catalina.startup.Catalina.await(Catalina.java:819)
  at org.apache.catalina.startup.Catalina.start(Catalina.java:765)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:498)
  at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:309)
  at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:443)
[2] Busy(0.9%) thread(2725/0xaa5) stack of java process(2721) under user(admin):
"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x00002ba81004c000 nid=0xaa5 waiting on condition [0x0000000000000000]
 java.lang.Thread.State: RUNNABLE
[3] Busy(0.0%) thread(3221/0xc95) stack of java process(3221) under user(admin):
[4] Busy(0.0%) thread(2721/0xaa1) stack of java process(2721) under user(admin):

以下是常用的几个参数及选项使用示例:

#每隔2秒执行一次执行结果,共执行5次(每次默认显示应用进程中前5个使用CPU高的线程)。
edas busy-threads 2 5

#显示指定Java进程的前5个使用CPU高的线程,[$JVM_PID]为Java进程的进程号,您可以使用ps -ef |grep java命令查看Java进程的进程号。
edas busy-threads -p [$JVM_PID]

#显示Java进程中前10个使用CPU高的线程。
edas busy-threads -c 10

#显示最近的2秒种内,CPU使用较高的线程。
edas busy-threads --current

使用开源工具诊断

show-busy-java-threads

在非ECS集群的环境中,可以使用show-busy-java-threads开源脚本来找到占用指定进程中排名前几位CPU高的线程。执行以下命令,下载并使用show-busy-java-threads脚本。具体使用方法,请参见show-busy-java-threads

wget --no-check-certificate https://raw.github.com/oldratlee/useful-scripts/release/show-busy-java-threads
chmod +x show-busy-java-threads
./show-busy-java-threads

Arthas

除了该脚本外,还可以使用阿里巴巴的Java问题综合诊断工具Arthas。该工具也可以用来显示指定JVM进程中使用CPU的排名前几位的线程。执行以下命令,显示当前连接的JVM进程中CPU占用排名前3名的线程及StraceTrace信息。

说明 Arthas的下载方法请参见Arthas官网
thread -n 3

系统显示类似如下。

"as-command-execute-daemon" Id=29 cpuUsage=75% RUNNABLE
  at sun.management.ThreadImpl.dumpThreads0(Native Method)
  at sun.management.ThreadImpl.getThreadInfo(ThreadImpl.java:440)
  at com.taobao.arthas.core.command.monitor200.ThreadCommand$1.action(ThreadCommand.java:58)
  at com.taobao.arthas.core.command.handler.AbstractCommandHandler.execute(AbstractCommandHandler.java:238)
  at com.taobao.arthas.core.command.handler.DefaultCommandHandler.handleCommand(DefaultCommandHandler.java:67)
  at com.taobao.arthas.core.server.ArthasServer$4.run(ArthasServer.java:276)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
  at java.lang.Thread.run(Thread.java:745)
  Number of locked synchronizers = 1
  - java.util.concurrent.ThreadPoolExecutor$Worker@6cd0b6f8
"as-session-expire-daemon" Id=25 cpuUsage=24% TIMED_WAITING
  at java.lang.Thread.sleep(Native Method)
  at com.taobao.arthas.core.server.DefaultSessionManager$2.run(DefaultSessionManager.java:85)
"Reference Handler" Id=2 cpuUsage=0% WAITING on java.lang.ref.Reference$Lock@69ba0f27
  at java.lang.Object.wait(Native Method)
  - waiting on java.lang.ref.Reference$Lock@69ba0f27
  at java.lang.Object.wait(Object.java:503)
  at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133) 

使用传统方式诊断

在不能连接公网的情况下,您可以使用以下方法来获取CPU占用较高的线程。执行以下命令,收集指定Java进程的信息。

top -Hbp [$JVM_PID] -d 1 -n 1 >> top.[$JVM_PID].txt && jstack [$JVM_PID] >> jstack.[$JVM_PID].txt
  1. 从收集到的top.XXX.txt中找到CPU占用率最高的线程ID(注意top.XXX.txtjstack.XXX.txt文件中的线程堆栈信息一一对应)。
  2. 执行以下命令,将找到的线程ID(十进制的数字)转换成十六进制。
    printf %x [$Thread_ID]
    说明 [$Thread_ID]为第1步找到的线程ID。
  3. 用这些转换后的十六进制的线程ID去jstack.XXX.txt文件中搜索,即可找到对应线程的信息。