定位CPU的问题一般可以分为以下几个步骤:
1. 定位进程
2. 定位线程
3. 查看线程信息
4. 定位具体方法(代码)
### 定位进程
通过`top -c`(然后按`P`按cpu排序),`htop`等工具定位到具体的高CPU进程
假设定位到的进程ID为14279
或者使用jps查看java进程号
### 定位线程
`top -H -p 14279`定位占cpu的线程:
```
%Cpu(s): 0.5 us, 0.7 sy, 0.0 ni, 98.8 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 8168236 total, 231696 free, 3660496 used, 4276044 buff/cache
KiB Swap: 969964 total, 969964 free, 0 used. 4197860 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
14293 faceless 20 0 4508772 97036 18112 S 45 1.2 152:35.42 java
14279 faceless 20 0 4508772 97036 18112 S 23 1.2 0:00.00 java
14282 faceless 20 0 4508772 97036 18112 S 0.0 1.2 0:00.37 java
```
一般超过80%就是比较高的,80%左右是合理情况
### 查看线程信息
方法一:手动定位
使用jstack 分别找也上面的线程的具体内容,比如第一个线程 14293:
```
# 将线程ID转换为16进制
```
printf '0x%x' 14293 => 0x37d5
```
# 通过jstack查看进程中该线程的信息:
`jstack pid | grep tid`找到线程堆栈
jstack 12816 | grep 0x3211 -A 30
# 【示例输出】
"VM Periodic Task Thread" os_prio=0 tid=0x00007ff1802d5800 nid=0x37d5 waiting on condition
```
当然这里也可以直接使用`jstack 14279 > ~/tmp/pid-14279.log`显示所有线程,然后手动寻找对应的ID
当然更常见的是我们对整个jstack文件进行分析,通常我们会比较关注WAITING和TIMED\_WAITING的部分,BLOCKED就不用说了。我们可以使用命令`cat jstack.log | grep "java.lang.Thread.State" | sort -nr | uniq -c`来对jstack的状态有一个整体的把握,如果WAITING之类的特别多,那么多半是有问题啦
方法二:通过arthas定位
[Arthas]支持直接通过`thread`子命令显示占用cpu最高的n个线程
展示当前最忙的前3个线程并打印堆栈:
```
$ 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)
```
根据Arthas`thread`命令打印出来的堆栈信息定位具体的业务代码,review代码并尝试定位逻辑。如有必要,
还可以通过`watch`子命令监听某个方法的调用次数和资源占用情况
> 要注意的是,arthas的cpu占比,和前面两种cpu占比统计方式不同。前面两种针对的是Java进程启动开始到现在的cpu占比情况,arthas这种是一段采样间隔内,当前JVM里各个线程所占用的cpu时间占总cpu时间的百分比
### 定位具体方法
1) GC线程
如果是GC线程一直在占用cpu,那么就基本确定是内存泄漏。进一步按照内存问题定位
2) 业务线程
如果是业务线程,那么根据下一节的方法,继续定位是哪些代码占用cpu
* io wait
* 比如磁盘空间不够导致的io阻塞
* 等待内核态锁,如 synchronized
* `jstack -l pid | grep BLOCKED`查看阻塞态线程堆栈
* dump 线程栈,分析线程持锁情况
* arthas提供了`thread -b`,可以找出当前阻塞其他线程的线程。针对 synchronized 情况
- 前言
- Write once, run anywhere
- 概述
- JAVA虚拟机
- JVM整体结构
- JVM架构模型
- JVM虚拟机分类
- HotSpot VM
- JRockit
- IBM-J9
- Azul/zing VM
- Taobao VM
- Dalvik VM
- Graal VM
- JAVA源码编译机制
- Javac编译器
- 分析和输入到符号表
- 注解处理
- 语义分析和生成class文件
- ECJ编译器
- 类执行机制
- 字节码解释执行
- 栈顶缓存
- 部分栈帧共享
- 编译执行
- 即时编译器
- C1 Compiler
- C2 Compiler
- Graal编译器
- C1与C2编译器
- AOT
- 编译优化
- 字符串优化
- 方法内联
- 逃逸分析
- 同步消除
- 标量替换
- 栈上分配
- 去虚拟化/逆优化
- 多层编译
- JVM编译策略
- OSR编译
- 冗余削除
- CodeCache
- 常量编译优化
- JVM运行时数据区
- 程序计数器
- JAVA虚拟机栈
- 栈帧
- 局部变量表
- 操作数栈
- 本地方法栈
- Java调用native方法
- JVM Stacks && Native Stacks
- 堆-Heap
- 方法区(Method Area)
- 运行时常量池
- 常量传播优化
- MetaSpace
- 直接内存
- StackOverflowError
- 递归方法
- OutOfMemoryError
- 本地内存溢出
- 执行引擎
- 运行时数据区关联关系
- jdk8内存结构
- JMM内存模型
- JAVA内存模型
- JMM八种操作指令
- 内存屏障
- 指令重排
- as-if-serial语义
- Happen-Before规则
- 数据依赖性
- 原子性、可见性与有序性
- 伪共享
- CPU三级缓存
- 缓存行
- MESI协议
- Java中的伪共享
- ConcurrentHashMap伪共享解决方案
- 虚拟机对象
- 对象创建原理
- 对象内存布局
- 对象头
- 实例数据
- 对象的访问定位
- 垃圾收集器与内存分配策略
- GC相关概念
- TLAB
- JVM GC工作原理
- 内存管理
- JAVA引用分类
- 死亡标记
- 回收方法区
- 三色标记算法
- 垃圾收集算法
- 标记-清除算法
- 标记-整理算法
- 复制算法
- 分代收集算法
- HotSpot算法实现
- STW
- 垃圾收集器
- 常见的垃圾收集器
- 垃圾收集器分类
- Serial收集器
- Serial Old收集器
- ParNew收集器
- Parallel Scavenge收集器
- Parallel Old收集器
- CMS收集器
- CMS完整收集过程
- Card Table
- G1收集器
- 分代收集
- 空间整合
- 可预测的停顿时间模型
- G1&CMS
- 主要参数说明
- G1适用场景
- Remembered Set
- G1垃圾回收的过程
- G1优化建议
- Shenandoah
- ZGC
- 垃圾收集器特点
- GC日志
- GC策略的评价指标
- jvm card table数据结构
- 对象生存轨迹
- 类文件结构
- 魔数
- 版本号
- 常量池
- 访问标志
- 父类索引
- 接口集合
- 字段集合
- 方法集合
- 属性集合
- 类加载机制与类的初始化
- Java代码执行流程
- 类加载过程
- 抽象类ClassLoader
- 常见类加载器
- BootstrapClassLoader
- 自定义类加载器
- 线程上下文类加载器
- 双亲委派模型
- Tomcat类加载机制
- ServiceLoader
- 类的初始化
- 常见的JVM类加载异常
- ClassNotFoundException
- NoClassDefFoundError
- LinkageError
- ClassCastException
- 虚拟机性能调优监控与故障处理工具
- CPU利用率高/飙升
- 排查及解决方案
- 上下文切换
- GC问题定位解决方案
- prommotion failed
- FullGC频繁
- youngGC
- 内存问题
- 内存溢出和内存泄漏
- 内存溢出
- 栈溢出
- 堆溢出
- 对外内存溢出
- 内存泄漏
- 磁盘问题
- 线上问题解决方案
- 不定期出现的接口耗时现象
- 线程池异常
- 死锁问题
- JVM调优
- jvm参考配置
- jvm-jstat
- jvm-jmap
- jvm-jstack
- jinfo
- jps
- 虚拟机的退出
- Shutdown Hook
- JVM指令
- 附录
- 常用JVM指令
- Class文件版本号
- Class文件格式
- 方法访问标识
- jvm常量池
- 类或接口的访问标识
- 描述符标识字符含义
- 字段访问标识
- Java程序与Docker容器环境
- 基准测试