Java应用性能分析工具:async-profiler

释放双眼,带上耳机,听听看~!

https://www.jianshu.com/p/9364028cca4e

厉害的内容

及时对项目进行性能检测,并且分析检测结果数据,发现热点代码是一项充满意义的工作,因为可能因为某一段热点代码会拖慢整个系统的运行,这是不可忍受的,发现热点代码之后需要及时进行代码优化,并且重复检测,多多角度检测,来360无死角的发现项目的性能瓶颈,让运行着的项目是最优化的。这也是每一位开发者的义务。

发现热点代码的前提是可以获取java应用运行时的profile数据,而采集这些数据需要较为底层的技术,还好目前有大量的开源根据可以进行这项非常有挑战性的工作,但是似乎每种工具采集的数据都有所差异(待考证),本文将介绍一种强大而轻量级的java应用运行时profile数据采集工具,在此之前,可以参考使用火焰图进行Java应用性能分析来大概了解java应用性能分析的一些基本情况,该文章中介绍的工具lightweight-java-profiler和本文要介绍的async-profiler都是类似的,但是前者我在自己的电脑上(macOS Sierra 10.12.4)上没有正常启动起来,错误大概是说段错误,但是可以在linux上正常启动并且可以采集到数据,结合火焰图生成工具FlameGraph可以进行java应用性能分析,喔对,你应该去学习一下如何从火焰图中找到热点代码,也就是你要学会看火焰图,这是一种非常重要的技能。也是进行应用性能分析的基础。下面开始详细介绍如何使用async-profiler来进行java应用性能分析。

环境准备

首先,你需要从github将代码下载下来:

https://github.com/jvm-profiling-tools/async-profiler\#async-profiler


1
2
3
4
5
1
2git clone https://github.com/jvm-profiling-tools/async-profiler
3
4
5

然后,进入到下载好的项目中,然后进行编译:


1
2
3
4
5
6
1
2cd async-profiler
3make
4
5
6

等待编译完成,可以在看到项目中多了一个build文件夹,这就是我们需要的东西,值得注意的是,async-profiler是少有的我在编译的时候没有遇到任何问题的工具,这也说明这个工具的易用性。当然,下面这些内容是必须的:

  • JAVA_HOME
  • GCC

关于async-profiler到底能做些什么事情,可以参考下面的描述:


async-profiler can trace the following kinds of events:

  • CPU cycles
  • Hardware and Software performance counters like cache misses, branch misses, page faults, context switches etc.
  • Allocations in Java Heap
  • Contented lock attempts of Java monitors

我主要关心的是CPU profiling这一个功能点,所以本文的重点也在CPU profiling这一个功能点上,其他的功能点可以自行去探索。关于async-profiler实现CPU profiling的原理以及为什么这么做,直接参考github上的readme就可以了,就不再这里赘述了,下面来看一下到底如何使用这个工具进行java应用的性能分析。

可以发现在async-profiler项目中有一个脚本叫做“profile.sh”,运行这个脚本,会输出如下提示内容:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
1
2Usage: ./profiler.sh [action] [options] <pid>
3Actions:
4  start             start profiling and return immediately
5  stop              stop profiling
6  status            print profiling status
7  list              list profiling events supported by the target JVM
8  collect           collect profile for the specified period of time
9                    and then stop (default action)
10Options:
11  -e event          profiling event: cpu|alloc|lock|cache-misses etc.
12  -d duration       run profiling for <duration> seconds
13  -f filename       dump output to <filename>
14  -i interval       sampling interval in nanoseconds
15  -b bufsize        frame buffer size
16  -t                profile different threads separately
17  -o fmt[,fmt...]   output format: summary|traces|flat|collapsed
18
19<pid> is a numeric process ID of the target JVM
20      or 'jps' keyword to find running JVM automatically using jps tool
21
22Example: ./profiler.sh -d 30 -f profile.fg -o collapsed 3456
23         ./profiler.sh start -i 999000 jps
24         ./profiler.sh stop -o summary,flat jps
25
26
27

其中几个重要的命令解释如下:

  • start : 开始进行应用的profile数据采集,如果没有设定采集时间的话会一直运行下去直到遇到stop命令
  • stop: 和start配合使用,用来停止应用的profile数据采集
  • status:检测工具的运行状态,比如可以看到是否已经不可用,或者已经运行多少时间了等信息
  • list:将可以采集的profile数据类型打印出来
  • -d N: 设定采集应用profile数据的时间,单位为秒
  • -e event:指定采集数据类型,比如cpu

其他的命令可以参考说明,并且可以结合自己实际操作来查看效果。下面来开始使用async-profiler工具来采集cpu profile数据,并且配合火焰图生成工具工具FlameGraph来生成cpu火焰图,并且从火焰图中找到热点代码。FlameGraph工具可以直接下载下来就可以使用:


1
2
3
4
5
1
2git clone https://github.com/brendangregg/FlameGraph
3
4
5

首先将java应用运行起来,你可以试着运行下面的代码来进行测试:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
1
2import java.io.File;
3
4class Target {
5    private static volatile int value;
6
7    private static void method1() {
8        for (int i = 0; i < 1000000; ++i)
9            ++value;
10    }
11
12    private static void method2() {
13        for (int i = 0; i < 1000000; ++i)
14            ++value;
15    }
16
17    private static void method3() throws Exception {
18        for (int i = 0; i < 1000; ++i) {
19            for (String s : new File("/tmp").list()) {
20                value += s.hashCode();
21            }
22        }
23    }
24
25    public static void main(String[] args) throws Exception {
26        while (true) {
27            method1();
28            method2();
29            method3();
30        }
31    }
32}
33
34
35

运行起来之后,可以使用jps命令来查看运行起来的java应用的pid,然后使用下面的命令开始使用工具进行cpu profile数据采集:


1
2
3
4
5
1
2./profiler.sh start $pid
3
4
5

一段时间之后,比如30秒后,就可以使用下面的命令来停止数据采集了:


1
2
3
4
5
1
2./profiler.sh stop $pid
3
4
5

然后,会打印处下面的信息:

可以很直观的看出,占用cpu时间最多的是method3,占用了93.06%的cpu时间,然后是method2和method1,分别占用2.93%和2.77%的cpu时间,所以很明显method3就是性能瓶颈,也就是所谓的热点代码,需要着手进行优化。当然,上面是有的命令式是比较简单的,下面来介绍一个比较厉害的命令,可以设定采集数据的时间,并且可以将采集到的数据dump起来,然后使用FlameGraph工具来生成火焰图进行直观的分析。当然,首先需要运行起来代码,并且使用jps找到应用的pid,然后可以使用下面的命令来进行数据采集任务:


1
2
3
4
5
1
2./profiler.sh -d 10 -o collapsed -f /tmp/collapsed.txt pid
3
4
5

这个命令的意思是说,采集数据的时间为10秒,并且将数据按照collapsed规范进行dump,并且dump到/tmp/collapsed.txt这个文件,过了10秒之后,工具会自动停止,并且将cpu的profile数据dump到指定的路径(按照指定的规范),可以到/tmp/collapsed.txt查看具体的文件内容,但是很大程度上是看不懂的,所以需要使用FlameGraph工具来进行加工一下,可以使用下面的命令来生成火焰图:


1
2
3
4
5
1
2~/github/FlameGraph/flamegraph.pl --colors=java /tmp/collapsed.txt > flamegraph.svg
3
4
5

当然,你需要指定你自己的FlameGraph的路径,上面命令中的是我的路径,很快,你就可以在当前目录下发现多了一个flamegraph.svg文件,使用chorm打开,就可以看到下面的图片内容(可以点击放大的):

可以看到,method3是最宽的,也就代表method3占用的cpu时间是最多的,这样看起来就直观很多了。

下面来看一下alloc类型的数据式怎么生成的,可以从这些数据中看出什么,运行下面的代码:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
1
2import java.util.concurrent.ThreadLocalRandom;
3
4public class AllocatingTarget implements Runnable {
5    public static volatile Object sink;
6
7    public static void main(String[] args) {
8        new Thread(new AllocatingTarget(), "AllocThread-1").start();
9        new Thread(new AllocatingTarget(), "AllocThread-2").start();
10    }
11
12    @Override
13    public void run() {
14        while (true) {
15            allocate();
16        }
17    }
18
19    private static void allocate() {
20        if (ThreadLocalRandom.current().nextBoolean()) {
21            sink = new int[128 * 1000];
22        } else {
23            sink = new Integer[128 * 1000];
24        }
25    }
26}
27
28
29

然后使用jps命令取到该应用的pid,然后执行下面的命令:


1
2
3
4
5
1
2./profiler.sh start  -e alloc pid
3
4
5

一段时间之后,可以使用下面的命令来停止数据采集:


1
2
3
4
5
1
2./profiler.sh stop  -e alloc pid
3
4
5

然后就会看到下面的输出:

可以看出各种类型的对象生成量,并且可以看到是从什么路径生成的(所谓路径就是类->方法->方法->…),当然,这只是该工具的一种玩法,其他复杂而有趣的玩法需要不断挖掘,并且结合实际应用来发现。

作者:一字马胡
链接:https://www.jianshu.com/p/9364028cca4e
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

给TA打赏
共{{data.count}}人
人已打赏
安全技术

详解Node.js API系列 Crypto加密模块(2) Hmac

2021-12-21 16:36:11

安全技术

从零搭建自己的SpringBoot后台框架(二十三)

2022-1-12 12:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索