Java应用性能CPU方面分析时,首先分析是否充分使用了CPU,尽量做到没有丝毫浪费,一般配合CPU使用率来分析CPU是如何被消耗的。CPU使用率分为用户态使用率和系统态使用率。用户态使用率是指执行应用程序代码的时间占总CPU时间的百分比,系统态使用率是指执行系统调用的时间占总CPU时间的百分比。系统态使用率高意味着共享资源有竞争或者I/O设备之间有大量的交互。提高应用性能和扩展性的一个目标是尽可能地降低系统态CPU使用率。除了使用率外,还需要监控每时钟指令数IPC或每指令时钟周期CPI等指标。Linux操作系统中常用的监控命令:top、vmstat、mpstat等,通常us表示用户态使用率,sy表示系统态使用率,id或idl表示空闲率,wt表示IO等待率。
[root@server-d37cc159641 ~]# vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 3567888 307764 2718880 0 0 0 12 5 6 0 0 100 0 0
0 0 0 3567544 307764 2718880 0 0 0 0 386 500 1 1 99 0 0
0 0 0 3567488 307764 2718884 0 0 0 0 380 496 1 1 99 0 0
4 0 0 3566280 307764 2718888 0 0 0 0 423 573 1 1 99 0 0
0 0 0 3567428 307764 2718892 0 0 0 60 448 593 1 1 98 1 0
0 0 0 3568192 307764 2718896 0 0 0 0 269 372 0 1 100 0 0
Windows操作系统则可以通过Ct + Alt + Del快捷键打开任务管理器和Win + R 输入perfmon进行查看,或者Win + R 输入cmd进入命令控制台通过typeperf命令查看perfmon对应计数器路径的方式进行监控。
除CPU使用率外,CPU程序运行队列情况也是主要监控目标之一。如果在很长一段时间运行队列的长度一直超过虚拟处理个数(Java API Runtime.availableProcessors()返回值)1倍时就需要关注了,只是此时还不需要采取行动,当长时间内运行队列长度达到虚拟处理4倍或以上时,则需要立即采取行动。解决办法有两种:一种是增加CPU以分担负载。另外一种则是分析系统的应用是否存在需要优化的算法和数据结构,并进一步优化它。Linux操作系统下可以通过vmstat输出的第一列便是运行的队列数:
[root@server-d37cc159641 ~]# vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 3567888 307764 2718880 0 0 0 12 5 6 0 0 100 0 0
0 0 0 3567544 307764 2718880 0 0 0 0 386 500 1 1 99 0 0
0 0 0 3567488 307764 2718884 0 0 0 0 380 496 1 1 99 0 0
4 0 0 3566280 307764 2718888 0 0 0 0 423 573 1 1 99 0 0
0 0 0 3567428 307764 2718892 0 0 0 60 448 593 1 1 98 1 0
0 0 0 3568192 307764 2718896 0 0 0 0 269 372 0 1 100 0 0
Windows操作系统可以通过typeperf "\System\Processor Queue Length" -si 1命令来查看:
C:\Users\lzy>typeperf "\System\Processor Queue Length" -si 1
"(PDH-CSV 4.0)","\\LAPTOP-MCN8UET0\System\Processor Queue Length"
"02/16/2021 09:21:52.209","37.000000"
"02/16/2021 09:21:53.467","4.000000"
"02/16/2021 09:21:54.500","29.000000"
"02/16/2021 09:21:55.570","16.000000"
"02/16/2021 09:21:56.674","5.000000"
"02/16/2021 09:21:57.686","1.000000"
"02/16/2021 09:21:58.694","1.000000"
"02/16/2021 09:21:59.700","1.000000"
"02/16/2021 09:22:00.715","0.000000"
"02/16/2021 09:22:01.731","15.000000"
"02/16/2021 09:22:02.786","18.000000"
"02/16/2021 09:22:03.831","15.000000"
"02/16/2021 09:22:04.844","12.000000"
第一列为采集时间点,第二列为运行的队列数。
当系统配置开启swap虚拟内存交换,在应用需要的内存超过系统可用的物理内存时操作系统会把应用中最少运行的那部分置换到磁盘swap空间去,这会由于磁盘读写效率影响应用程序的性能,例如JVM垃圾回收时需要频繁读取内存的对象时,当读取swap那部分数据时,性能会急剧下降,由于JVM垃圾回收存在Stop-The-Work的特性,理论上可能会导致应用程序长时间阻塞等待。Windows操作系统中可以通过如下命令查看:
typeperf "\Memory\Available Mbytes" "\Memory\Pages/sec" -si 3
输出如下:
"02/15/2021 22:12:37.824","2345.000000","0.660833"
"02/15/2021 22:12:40.840","2344.000000","0.328405"
"02/15/2021 22:12:43.976","2344.000000","0.322099"
"02/15/2021 22:12:46.986","2345.000000","0.662745"
"02/15/2021 22:12:50.005","2339.000000","1.326349"
"02/15/2021 22:12:53.019","2338.000000","0.664177"
"02/15/2021 22:12:56.032","2338.000000","21.856496"
第一列时间戳,第二列为可用物理内存,单位是MB,第三列为每秒的页面调度。分析思路如下:当第三列没有页面调度时,一定没有发生任务swap页面交换,不管第二列系统内存是否紧张,但当第三列发生页面调度时,此时需要观察第二列是否同时耗用了一定的内存,如果此时系统内存保持稳定不变,则说明此时大概率是发生了swap页面交换。Linux操作系统可以通过vmstat命令来分析,效果如下:
[root@server-d37cc159641 ~]# vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 3568872 307764 2716820 0 0 0 13 3 4 0 0 100 0 0
0 0 0 3568752 307764 2716820 0 0 0 0 457 608 1 1 98 0 0
0 0 0 3568752 307764 2716824 0 0 0 0 382 508 0 1 99 0 0
0 0 0 3568908 307764 2716828 0 0 0 60 384 522 0 0 99 0 0
0 0 0 3568680 307764 2716828 0 0 0 0 525 681 1 2 98 0 0
0 0 0 3568948 307764 2716832 0 0 0 0 394 508 0 1 99 0 0
重点观察free、swap和si和so列,fred表示空闲内存,swap表示磁盘内存,si表示内存页面换入,so表示内存页面换出。当swap不为0时表示发生过swap页面交换。
在Java应用程序中,当存在大量的锁竞争时会因为等待锁导致程序阻塞而影响程序的性能,Java5或以上的 HotSpot VM增加了锁优化机制,当由于线程锁竞争过大无法立刻获取到锁时,首先线程会通过忙循环自旋的方式尝试获得锁,如果若干次忙循环自旋之后仍然没有获取到锁,则挂起该线程,等待被唤醒再次尝试获取该锁。挂起和唤醒线程会导致操作系统的让步式上下文切换,因此锁竞争严重的应用会表现出大量的让步式上下文切换,让步式上下文切换耗费的时钟周期代价非常高,大概高达80000个时钟周期。Linux操作系统可以通过sysstat工具包的pidstat命令来分析java应用锁竞争情况:
# 安装sysstat工具包
yum install sysstat -y
# 找到需要监控的java应用pid
ps aux |grep java
# 执行监控
pidstat -w -I -p 1234 5
# 输出如下:
11时17分53秒 UID PID cswch/s nvcswch/s Command
11时17分58秒 0 1234 10.00 0.00 wrapper
11时18分03秒 0 1234 10.00 0.00 wrapper
11时18分08秒 0 1234 10.00 0.00 wrapper
11时18分13秒 0 1234 10.00 0.00 wrapper
11时18分18秒 0 1234 10.00 0.00 wrapper
11时18分23秒 0 1234 9.80 0.00 wrapper
11时18分28秒 0 1234 10.00 0.00 wrapper
pidstat -w输出中的cswch/s就是让步式上下文切换信息。该信息包含所有虚拟处理器的让步式上下文切换,一般分析计算方式如下:
1、单个虚拟处理器让步式上下文切换 = cswch / 虚拟处理器core数;
2、让步式上下文切换耗费的时钟周期 = 单个虚拟处理器上下文切换 * 80000;
3、浪费的上下文时钟周期百分比 = 让步式上下文切换耗费的时钟周期 / CPU每秒的时钟周期数(可通过/proc/cpuinfo进行查看,一般为3GHz即3000000000);
我们将最终得到的浪费的上下文时钟周期百分比如果在3% ~ 5%或以上时,说明Java应用锁竞争较大。
Windows操作系统可通过Intel VTune或AMD CodeAnalyst工具进行分析,这里不过多介绍。
上面只是对Java锁竞争情况的分析,可以通过线程转储或借助Oracle Solaris Studio Performance Analyzer工具进行竞争锁查找,进行代码的定位。
Tips: 相对让步式上下文切换,抢占式上下文切换是指线程因为分配的时间片用尽而被迫放弃CPU,或者被其它优先级更高的线程所抢占。抢占式上下文切换高表示运行的线程数大于可用的虚拟处理器,此时通过vmstat通常就能看到很长的运行队列正在排队等待继续运行。
待运行线程在处理器直接迁移也会导致性能下降。通常操作系统的CPU调度程序会将待运行的线程迁移到上次运行他的虚拟处理器,如果这个虚拟器忙,调度程序就会将其迁移到其它可用的虚拟处理器进行运行。线程迁移会对应用性能造成影响,这是因为新的虚拟处理器缓存中可能没有待运行线程所需的数据或状态信息。多核系统上运行Java应用可能会发生大量的线程迁移,减少迁移的策略是创建处理器组并将Java应用分配给这些处理器组。一般来说,如果每秒迁移超过500次,将Java应用绑定在处理器组上可以得到一定的性能提升。
Linux操作系统下可以通过第三方工具nicstat监控网络I/O使用率,下面是安装nicstat工具步骤:
# 下载tar包
wget http://sourceforge.net/projects/nicstat/files/nicstat-1.92.tar.gz
# 解压缩tar包
tar -zxvf nicstat-1.92.tar.gz
# 进入目录
cd nicstat-1.92
# 重命名文件
cp Makefile.Linux Makefile
# 如果是64位的操作系统,则需要这一步的修改
vi Makefile
CFLAGS = $(COPT) -m32 #将此行修改为如下:
CFLAGS = $(COPT)
# 编译和安装
make && make install
安装完成之后,可以通过./nicstat.sh可执行文件进行网络I/O使用率的监控,该命令可用选项如下:
-h #显示简单的用法
-v #显示nicstat版本
-n #只统计非本地(即非回环)接口
-s #显示摘要输出(只是接收和发送的数据量)
-x #显示扩展的输出
-M #以Mbps显示吞吐量,而不是默认的KB/s
-p #以解析后的输出格式显示
-z #跳过采样周期内是零流量的接口
-t #tcp流量统计
-u #ucp流量统计
-a #等同于'-x -t -u'
-l #只显示端口状态
-i interface[,interface...]#指定接口
例如每隔三秒采集一次的效果如下所示:
[root@server-d37cc159641 nicstat-1.92]# ./nicstat.sh 3
Time Int rKB/s wKB/s rPk/s wPk/s rAvs wAvs %Util Sat
13:14:34 eth0 0.96 1.08 3.30 3.65 297.3 303.1 0.00 0.00
13:14:34 lo 0.05 0.05 0.18 0.18 258.9 258.9 0.00 0.00
Time Int rKB/s wKB/s rPk/s wPk/s rAvs wAvs %Util Sat
13:14:37 eth0 1.40 3.16 18.32 26.98 78.15 119.9 0.00 0.00
13:14:37 lo 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Time Int rKB/s wKB/s rPk/s wPk/s rAvs wAvs %Util Sat
13:14:40 eth0 1.35 2.83 17.67 26.67 77.96 108.7 0.00 0.00
13:14:40 lo 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
输入字段含义如下:
Time #抽样结束的时间
Int #网卡名
rKB/s,InKB #每秒读的千字节数(received)
wKB/s,OutKB #每秒写的千字节数(transmitted)
rMbps,RdMbps #每秒读的百万字节数K(received)
wMbps,WrMbps #每秒写的百万字节数M(transmitted)
rPk/s,InSeg,InDG #每秒读的数据包
wPk/s,OutSeg,OutDG #每秒写的数据包
rAvs #平均读的数据包大小
wAvs #平均写的数据包大小
%Util #接口的利用率百分比
Sat #每秒的错误数,接口接近饱和的一个指标
Tips: 采用Java NIO比阻塞的java.net.Socket更高效,可以减少网络读写的系统调用,从而降低系统态CPU的使用率,建议使用Java NIO方面的框架进行实现,例如Netty。
Linux操作系统可以通过sysstat工具包下的iostat来监控磁盘使用率,可以通过以下命令安装:
yum install sysstat -y
安装完成后可以通过iostat -xm监控磁盘I/O使用率和系统态CPU使用率:
上图可以看到vda盘使用率为0.02%(%util列),系统态CPU使用率为0.12(%system列)。通常我们关系这两列的信息即可了解磁盘是否处于忙或不忙状态。
最后推荐一个好用命令行工具:sar。sar可以指定需要手机的数据,例如用户态CPU使用率、系统态CPU使用率、系统调用次数、内存页面调度和磁盘I/O数据。sar可以实时手机这些数据。
---------- 正文结束 ----------
长按扫码关注微信公众号
Java软件编程之家