前言
在Java程序中,如果针对单个接口,我们是可以采用trace命令去查看接口的调用连耗时情况的。但是,针对整个项目,不知哪个任务CPU耗时过高的时候,就需要用到火焰图去排查具体问题了
1、首先来看一段简单的代码,这段代码能够让CPU保持相对稳定的运行,并且CPU的占用率较低
object TestMain {
fun task2() {
Thread {
while (true) {
Thread.sleep(1000)
println("task2:cpu飙升中~")
}
}.start()
}
fun task1() {
Thread {
while (true) {
Thread.sleep(1000)
println("task1:cpu飙升中~")
}
}.start()
}
fun task3() {
Thread {
while (true) {
Thread.sleep(1000)
println("task3:cpu飙升中~")
}
}.start()
}
}
fun main() {
TestMain.task1()
Thread.sleep(100)
TestMain.task2()
Thread.sleep(100)
TestMain.task3()
}
2、我们使用 thread
命令查看下当前的CPU占用情况
3、我们来观察一下相对稳定情况下,火焰图的具体情况
涉及命令
profiler start 开始记录火焰图
profiler getSamples 当前采样数
profiler status 当前采样时长
profiler stop 结束记录火焰图
这里最重要的还是使用
profiler start
和profiler stop
命令,用于开始和结束记录火焰图,当结束记录火焰图后,就能获取类似于这样的地址/Users/abc/Documents/cvte-code/school-group-server/arthas-output/
,可以直接通过浏览器查看火焰图
4、我们再来查看一个模拟高CPU占用的场景代码
object TestMain {
fun task2() {
Thread {
while (true) {
println("task2:cpu飙升中~")
}
}.start()
}
fun task1() {
Thread {
while (true) {
println("task1:cpu飙升中~")
}
}.start()
}
fun task3() {
Thread {
while (true) {
println("task3:cpu飙升中~")
}
}.start()
}
}
fun main() {
TestMain.task1()
Thread.sleep(100)
TestMain.task2()
Thread.sleep(100)
TestMain.task3()
}
5、使用 thread
命令查看一下CPU占用率,这时候CPU飙高,出现了线程阻塞 BLOCKED
6、再来看下此时的火焰图
火焰图分析
图中颜色代表
- 绿色:Java代码
- 黄色:JVM,C++代码
- 红色:用户态,C代码
- 橙色:内核态,C代码
图中x-y轴代表
- x轴代表的不是时间,而是采样总量
- y轴代表方法的调用栈深度,倘若方法调用得越多,火焰越高,顶部的栈就是当前正在执行的方法
栈宽含义(CPU时间)
- 宽度可以理解为CPU采样率的占比,越宽代表当前栈在采样数中占比高,其可能为三种含义
- 该函数运行时间长
- 该函数被调用次数多
平顶现象(一定要格外注意)
- 平顶现象是由于当前程序的采样数在总采样数中占用过高导致的,出现这种现象需要特意关注一下程序具体的调用栈,采样比例占用率过高,即代表方法在CPU中的占用率过高
总结
火焰图只是用于辅助程序分析定位问题,查看程序在采样期间的大致情况,实际场景还需结合CPU占用率、查看JVM的DUMP快照等方式进行定位