1. 使用top命令找出CPU占用最多的应用
首先,你需要使用top命令来识别哪个进程正在使用大量的CPU资源。
- 运行top命令:
- 在终端中输入top并按下回车键。
- 查看CPU使用率最高的进程:
- 默认情况下,top会按CPU使用率排序。查看%CPU列,找出使用率最高的进程。
- 识别Java进程:
- 如果发现CPU使用率最高的是Java进程(在COMMAND列显示为java),记下它的PID。
2. 使用jps和jstack获取Java进程的线程堆栈跟踪
接下来,使用jps命令来确认Java进程的ID,然后用jstack来获取线程的堆栈跟踪。
- 使用jps定位Java进程:
- 运行jps -l以列出所有Java进程及其主类的全限定名。这有助于确认你在top中看到的Java进程。
- 使用jstack获取线程堆栈:
- 对于每个可疑的Java进程,使用jstack加进程ID来获取线程堆栈跟踪:jstack <PID>。这将输出所有线程的当前调用堆栈。
3. 分析堆栈跟踪
通过分析jstack输出的堆栈跟踪,你可以尝试找到CPU使用率高的线程。这里有些指标可以帮助你:
- 查找状态为RUNNABLE的线程,因为它们是实际在CPU上运行的线程。
- 检查这些线程的堆栈跟踪,寻找正在执行的方法和代码行。
- 注意看是否有特定的方法或循环被频繁调用,这可能是CPU使用率高的原因。
示例分析
假设你运行了jstack
,得到了类似以下的输出片段:
"Thread-1" #10 prio=5 os_prio=0 tid=0x00007f2a5400e800 nid=0x6e03 runnable [0x00007f2a4d9fa000]java.lang.Thread.State: RUNNABLEat MyAppPackage.MyBusyClass.busyMethod(MyBusyClass.java:50)at MyAppPackage.MyBusyClass.run(MyBusyClass.java:35)at java.lang.Thread.run(Thread.java:748)
这是一个示例堆栈跟踪,其中关键信息包括:
- 线程状态 (
java.lang.Thread.State
):RUNNABLE
表明该线程正在Java虚拟机中执行,是一个活跃运行的线程。 - 执行的方法和代码行:
MyBusyClass.busyMethod
在MyBusyClass.java:50
。这是线程正在执行的方法,如果这行代码或方法在多个堆栈跟踪中反复出现,可能表明它是CPU使用率高的原因。
通过查找此类模式,你可以识别出可能导致CPU使用率高的线程和代码段。
4. 进行代码级分析
一旦你发现了可能导致高CPU使用的线程和方法,你就需要查看相应的源代码。在代码中寻找如下问题:
- 无限循环或高效率循环。
- 性能低下的算法。
- 大量的同步操作或死锁。
- 不必要的资源密集型操作。
5. 修改代码
===============================================================
附录:
Top命令 主要输出指标的解释:
- PID: 进程标识符,用于唯一标识系统中的每个进程。
- COMMAND: 正在执行的命令的名称,这里是java。
- %CPU: 此进程使用的CPU百分比。
- TIME: 此进程累积占用的CPU时间。
- #TH: 进程使用的线程数
- #WQ: 在等待队列中的线程数量。
- #POR: 进程使用的端口数量。
- MEM: 进程占用的物理内存大小
- PURG: 被清理的内存大小。
- CMPR: 压缩的内存大小。
- PGRP: 进程组ID。
- PPID: 父进程的PID
- STATE: 进程的当前状态。
- BOOSTS: 任何优先级提升(或降低)的标记。
- %CPU_ME: 此进程自身的CPU使用率。
- %CPU_OTHRS: 其他进程的CPU使用率。
- UID: 用户标识符,标识运行此进程的用户
- FAULT: 页面错误的次数。
- COW: 写时复制的页面数。
- MSGSEN/MSGREC: 发送和接收的消息数。
- SYSBSD/SYSMACH: BSD和Mach系统调用的次数。
- CSW: 上下文切换次数。
- PAGE: 页面错误的次数。
- IDLE: CPU空闲时间的百分比。
- POWE: 能耗估算。
- INSTRS: 执行的指令数。
- CYCLES: 处理器周期数。
- USER: 运行此进程的用户名。
通过一个简化的 jstack 输出例子来解释其中的一些关键指标。这里是一个典型的 jstack 输出示例:
"main" #1 prio=5 os_prio=0 tid=0x00007f0f74008000 nid=0x1e15 waiting on condition [0x00007f0f74efb000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method) - locked <0x00000000f5a0f5b8> (a java.lang.ref.Reference$Lock) at com.example.MyClass.myMethod(MyClass.java:123)
现在,让我们解析这个输出:
- 线程名称和序号:
- "main" #1:这是线程的名称和序号。这里的线程名是 "main",通常是 Java 程序的主线程。#1 表示这是 JVM 中的第一个线程。
- 线程优先级:
- prio=5:这是线程的优先级,范围从 1(最低)到 10(最高)。这里的优先级是 5,是默认值。
- 操作系统线程优先级:
- os_prio=0:这是线程在操作系统层面的优先级。这个值依赖于操作系统和 JVM 的实现。
- 线程 ID:
- tid=0x00007f0f74008000:这是 JVM 内部为线程分配的唯一标识符,以十六进制表示。
- 本地线程 ID:
- nid=0x1e15:这是操作系统为线程分配的本地标识符,同样以十六进制表示。在某些情况下,可用于将 JVM 中的线程与操作系统级别的线程关联起来。
- 线程状态:
- java.lang.Thread.State: TIMED_WAITING (sleeping):这是线程的状态。TIMED_WAITING 通常意味着线程正在执行某种形式的等待,这里是由于 sleep 方法引起的。
- 方法调用:
- at java.lang.Thread.sleep(Native Method):这显示了线程在调用栈的当前位置。在这个例子中,线程正在执行 Thread.sleep 方法。
- at com.example.MyClass.myMethod(MyClass.java:123):这是调用栈的下一个记录。这表明 myMethod 在 MyClass.java 的第 123 行被调用。
- 锁信息:
- - locked <0x00000000f5a0f5b8>:这表示线程当前持有的锁。锁是在对象 0x00000000f5a0f5b8 上,这是一个内部的对象引用。
通过分析这些信息,可以获取线程的当前状态和活动,这对于诊断性能问题和并发问题非常有帮助。例如,如果多个线程都在等待同一个锁,那可能就是死锁的迹象。或者,如果一个线程长时间处于 RUNNABLE 状态并消耗大量 CPU,那可能是性能问题的源头。