上篇文章写完后,性能调优|成都核酸系统篇 收到了一些夸赞,让我有继续写下去的动力;
也有人提了一些意见,我也会尽可能满足。
Java 系统 CPU 占用分析工具
linux系统上,可以直接使用 perf 工具采样数据,然后用火焰图工具生成火焰图。那么Java是不是也可以使用perf呢?是的,也可以,但是需要安装一个perf-map-agent,把底层堆栈转换为Java可见代码,然后通过FlameGraph生成火焰图(profile是另外一个bcc的工具,性能消耗比perf还要低,也可以用CPU占用剖析)。
async-profiler
这里我们介绍一个更简单易用的工具 async-profiler。它是一款开源的 Java 性能分析工具,原理是基于 HotSpot 的 API,以微乎其微的性能开销收集程序运行中的堆栈信息、内存分配等信息进行分析。
async-profiler 不使用侵入性的技术,例如字节码检测工具或者探针检测等,这也说明 async-profiler 的内存分配分析像 CPU 性能分析一样,不会产生太大的性能开销,同时也不用写出庞大的堆栈文件再去进行进一步处理,。
async-profile 目前支持 Linux 和 macOS 平台(macOS 下只能分析用户空间的代码)。
- Linux / x64 / x86 / ARM / AArch64
- macOS / x64
async-profiler 可以跟踪以下类型的事件:
- CPU 周期
- 硬件和软件性能计数器,如缓存未命中、分支未命中、缺页异常、上下文切换等。
- Java 堆中的内存分配
- 锁尝试,包括 Java 对象监视器和 ReentrantLocks
使用方式
下载地址:
https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.8.3/async-profiler-2.8.3-linux-x64.tar.gz
cd async-profiler-2.8.3-linux-x64
./profile.sh
我经常使用分析主要包括:
- on cpu 占用,找到主要找到CPU消耗热点代码
- off cpu 占用分析,通过这个可以发现锁、IO消耗等;
- 堆内存分配占用,可以用来排查内存消耗过高等问题。
性能分析案例
- 查看服务进程pid
- 分析之前一定要有持续流量,否则可能会出现分析不准确或者没有采集到任何数据
- 执行命令开始on CPU分析,含义是对 1189878 进程采样30s,最后保存到s1.html中
./profiler.sh -d 30 -f s1.html 1189878
- 打开s1.html
- 其中火焰图里,横条越长,代表使用的越多,从下到上是调用堆栈信息。在这个图里可以看到序列化方法的 CPU 使用是最多的,点击这个方法。还可能看到关于这个方法更详细的信息。
- 在性能优化过程中,有时会出现性能无法提升的情况,可能是线程数量太少,CPU无法充分利用,也可能是IO等待、锁...导致,这时可以通过添加 -e wall 参数分析 off CPU,查看性能无法提升的原因
./profiler.sh -d 30 -e wall -f s2.html 1189878
- 内存分配分析,参数 -e alloc
./profiler.sh -d 30 -e alloc -f s3.html 1189878
分析容器中的Java应用程序
从主机进行分析时,pid应该是主机命名空间中的Java进程ID。使用ps aux | grep java
或docker top<container>
查找进程ID。
async-profiler应该由特权用户从主机运行 - 它将自动切换到正确的pid/装载命名空间,并更改用户凭据以匹配目标进程。还要确保目标容器可以通过与主机上相同的绝对路径访问libasyncProfiler.so
。
默认情况下,Docker container限制对perf_event_open syscall
的访问。因此,为了允许在容器中进行分析,您需要修改seccomp配置文件,或者使用--security-opt seccomp=unconfined
选项完全禁用它。此外需要添加 --cap-add SYS_ADMIN
。
详细请参考:https://github.com/jvm-profiling-tools/async-profiler#troubleshooting
另外一种简单分析方式
在服务运行过程中,通过Java自带jstack工具转储堆栈,通常可以看出服务中所有线程CPU占用,这种方式虽然简单,但是很难看到CPU占用全貌。具体步骤如下所示:
- 找到进程pid(可以通过jps)
- 查看最高CPU线程占用(top -Hp pid)
- 第一列pid编号转换为16进制(printf "%x\n" 46924)
- jstack 46924 | grep e58f
总结
有效提高服务性能的方法就是通过火焰图或者其它工具找到生产环境中的热点代码,如果这块代码是CPU/IO密集型的,意味着这块代码经常占用CPU/IO,就要查看这块代码的CPU/IO消耗是否合理?是否可以通过修改算法、升级版本来降低CPU消耗,然后修改部署,再次测试,如此反复。