4.性能优化-监控-硬件资源监控-CPU

本文介绍如何利用top、vmstat及pidstat等命令诊断Java应用的性能问题,包括CPU使用率过高、上下文切换频繁等问题,并提供了具体的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.概述
要提高应用的性能,就必须充分利用给配给它的CPU周期。
系统态CPU使用率高意味着共享资源有竞争或者IO设备之间有大量的交互。
对于java应用,典型的上下文切换发生在进行文件IO操作、网络IO操作、锁等待或者线程sleep时,当前线程进入阻塞状态或者休眠状态,从而触发上下文切换,上下文切换过多会使应用的响应速度下降。

2.top命令
# top命令
top - 21:19:01 up 33 min, 3 users, load average: 0.01, 0.02, 0.04
Tasks: 147 total, 1 running, 146 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.0%us, 0.7%sy, 0.0%ni, 99.0%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st
Mem: 1309844k total, 991356k used, 318488k free, 41140k buffers
Swap: 4161528k total, 0k used, 4161528k free, 245992k cached

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
19 root 20 0 0 0 0 S 0.3 0.0 0:04.93 ata/0
2650 root 20 0 98656 27m 22m S 0.3 2.1 0:03.36 vmtoolsd
3472 root 20 0 2656 1112 860 R 0.3 0.1 0:00.95 top
1 root 20 0 2828 1376 1180 S 0.0 0.1 0:01.42 init
2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd
3 root RT 0 0 0 0 S 0.0 0.0 0:00.00 migration/0
4 root 20 0 0 0 0 S 0.0 0.0 0:00.00 ksoftirqd/0
5 root RT 0 0 0 0 S 0.0 0.0 0:00.00 watchdog/0
6 root 20 0 0 0 0 S 0.0 0.0 0:00.01 events/0
第一部分:系统统计信息
第1行:任务队列信息。
21:19:01 --系统当前时间
33 min --系统运行时间
3 users --当前登录用户数
load average: 0.01, 0.02, 0.04 --系统的平均负载,即任务队列的平均长度,这3个值分别表示1分钟、5分钟、15分钟到现在的平均值。

第2行:进程统计信息。
147 total --进程总数
1 running --运行的进程数
146 sleeping --睡眠进程数
0 stopped --停止进程数
0 zombie --僵尸进程数
第3行:CPU统计信息
0.0%us --用户态CPU占用率
0.7%sy --系统态CPU占用率
0.0%ni --改变过优先级的CPU占用率
99.0%il --空闲CPU占用率
0.0%wa --IO等待CPU占用率
0.0%hi --硬件中断CPU占用率
0.3%si --软件中断CPU占用率
0.0%st --有虚拟CPU的情况,表示被虚拟机偷掉的CPU时间。
第4行:内存统计信息
1309844k total --物理内存总量
991356k used --已使用物理内存
318488k free --空闲物理内存
41140k buffers --用作内核缓存的内存量
第5行:
4161528k total --交换区总量
0k used --使用的交换区
4161528k free --空闲交换区
245992k cached --缓冲交换区总量

这里面有2个容易混淆的概念buffers和cached,前者是用来给块设备做缓存的大小,后者是用来对文件系统做缓存的大小。
空闲内存为318488k,这部分内容仅是指没有被分配的内容,实际的空闲内存是free+buffers+cached=318488k+41140k+245992k=605620k,可以通过free命令的第2行看到,这是由于Linux将暂时不用的内存做文件和数据的缓存,以提高性能,当应用程序需要用到内存时,这些缓存会自动释放。

第二部分:进程信息
PID --进程Id
USER --进程所有者的用户Id
PR --优先级
NI --nice值
VIRT --进程使用的虚拟内存量,单位kb。 VIRT=SWAP+RES
RES --进程使用的、未被换出的物理内存大小。
SHR --共享内存大小
S --进程状态。R:运行,S:睡眠
%CPU --CPU占用百分比
%MEM --内存占用百分比
TIME+ --进程使用CPU时间
COMMAND --进程命令

其他:
1 --查看多个CPU
shift+h --切换到线程
运行过程中输入M,则按内存排序 ,输入P按CPU排序。
f --进行列的选择
o --更改列的显示顺序
3.vmstat命令
#vmstat 3
[color=red][b]procs [/b][/color]-----------memory---------- --------------swap-- ---------------io---- --------[color=red][b]system-- -----cpu-[/b][/color]----
[color=red][b] r b[/b][/color] swpd free buff cache si so bi bo [color=red][b]in cs us sy id wa st[/b][/color]
0 0 90340 26756 20396 214080 503 1 0 100 90 206 0 1 95 3 0
0 0 90340 26756 20396 214084 0 0 0 0 58 144 0 0 100 0 0
0 0 90340 26632 20404 214084 48 0 0 0 85 185 0 0 100 0 0
0 0 90340 26632 20404 214084 0 0 0 0 63 145 0 0 100 0 0
0 0 90340 26632 20404 214084 43 0 0 0 80 176 0 0 100 0 0
0 0 90340 26632 20404 214084 0 0 0 0 69 155 0 0 100 0 0
0 0 90340 26632 20412 214084 16 0 0 0 76 174 0 1 99 0 0
0 0 90340 26632 20412 214084 0 0 0 0 76 160 0 0 100 0 0
procs:
r --等待运行的进程数
b --处于中断状态的进程数
memory:
swpd --虚拟内存使用情况 ,单位kb
free --空闲内存,单位kb
buffer --块设备缓存大小,单位kb
cache --文件缓存大小,单位kb
swap:
si --从磁盘交换到内存的页数量 ,单位kb/s
so --从内存交换到磁盘的页数量,单位kb/s
io:
bi --发送到块设备的块数,单位块/s
bo --从块设备接收的块数,单位块/s
system:
in --每秒的中端数
cs --每秒的上下文切换数
cpu:
us --用户CPU百分比
sy --内核CPU百分比
id --空闲CPU百分比
wa --IO等待CPU百分比
st --虚拟CPU百分比

其中加红加粗部分为CPU相关数据
4.pidstat命令
#pidstat -p <pid> -u -t 1 3
Linux 2.6.32-71.el6.i686 (centos) 06/20/2014 _i686_ (1 CPU)


04:39:03 PM TGID TID %usr %system %guest %CPU CPU Command
04:39:04 PM 3781 - 0.00 0.00 0.00 0.00 0 java
04:39:04 PM - 3781 0.00 0.00 0.00 0.00 0 |__java
04:39:04 PM - 3782 0.00 0.00 0.00 0.00 0 |__java
04:39:04 PM - 3783 0.00 0.00 0.00 0.00 0 |__java
04:39:04 PM - 3784 0.00 0.00 0.00 0.00 0 |__java

-u --CPU
-d --IO
-r --内存
-t --线程级别

TGID --进程Id
TID --线程Id
%usr --占用用户CPU百分比
%system --占用系统CPU百分比
%guest --虚拟处理器占用的CPU百分比
%CPU --占用CPU百分比
CPU --指示进程在哪个CPU上运行
Command --命令

通过此命令可以找出消耗CPU最多的线程id,然后通过jstack命令导出线程快照,从快照中就可以查找出消耗CPU最多的线程在运行什么代码,从而快速定位性能瓶颈代码,具体见us高章节。

5.CPU资源瓶颈
1.运行队列如果长时间是CPU个数的3~4倍以上,系统可能就存在性能瓶颈。

2.us+sy。理想情况下,应该尽量减少系统态CPU的消耗,让程序尽可能多的运行用户程序的计算,Linux System and NetWork Performance Monitoring中建议用户进程的CPU消耗/内核的CPU消耗的比率在65%~70%/30%~35%左右。一般情况下,如果%us+%sy大于75%就说明系统存在性能瓶颈了。
6.us高代码示例
代码:
public class HoldCPUMain {
public static class HoldCPUTask implements Runnable{
public void run(){
while(true){
double a = Math.random() * Math.random();
}
}
}
public static class LazyTask implements Runnable{
public void run(){
try{
Thread.sleep(1000);
}catch(Exception e){}
}
}
public static void main(String[] args) throws Exception{
new Thread(new HoldCPUTask()).start();
new Thread(new LazyTask()).start();
new Thread(new LazyTask()).start();
new Thread(new LazyTask()).start();
}
}

运行结果:
top - 19:28:31 up 5 min, 3 users, load average: 1.06, 1.10, 0.57
Tasks: 146 total, 1 running, 145 sleeping, 0 stopped, 0 zombie
Cpu(s): 99.7%us, 0.3%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 1309844k total, 987924k used, 321920k free, 54272k buffers
Swap: 4161528k total, 0k used, 4161528k free, 235556k cached

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
3085 root 20 0 407m 11m 7112 S 99.6 0.9 1:18.79 java

由于HoldCPUTask类的线程一直在进行计算,所以占用了大量的CPU资源。
可以通过pidstat命令找出占用CPU最高的线程。
1)通过jps命令找出运行HoldCPUMain的进程id
# jps
3085 HoldCPUMain
3188 Jps
2)执行pidstat -p 3085 -u -t 1 3
pidstat -p 3085 -u -t 1 3
Linux 2.6.32-71.el6.i686 (centos) 06/20/2014 _i686_ (1 CPU)

07:33:37 PM TGID TID %usr %system %guest %CPU CPU Command
07:33:38 PM 3085 - 100.00 0.00 0.00 100.00 0 java
07:33:38 PM - 3085 0.00 0.00 0.00 0.00 0 |__java
07:33:38 PM - 3086 0.00 0.00 0.00 0.00 0 |__java
07:33:38 PM - 3087 0.00 0.00 0.00 0.00 0 |__java
07:33:38 PM - 3088 0.00 0.00 0.00 0.00 0 |__java
07:33:38 PM - 3089 0.00 0.00 0.00 0.00 0 |__java
07:33:38 PM - 3090 0.00 0.00 0.00 0.00 0 |__java
07:33:38 PM - 3091 0.00 0.00 0.00 0.00 0 |__java
07:33:38 PM - 3092 0.00 0.00 0.00 0.00 0 |__java
07:33:38 PM - 3093 0.00 0.00 0.00 0.00 0 |__java
07:33:38 PM - [color=red][b]3094 100.00 0.00 0.00 100.00 0 |__java[/b][/color]

07:33:38 PM TGID TID %usr %system %guest %CPU CPU Command
07:33:39 PM 3085 - 98.00 0.00 0.00 98.00 0 java
07:33:39 PM - 3085 0.00 0.00 0.00 0.00 0 |__java
07:33:39 PM - 3086 0.00 0.00 0.00 0.00 0 |__java
07:33:39 PM - 3087 0.00 0.00 0.00 0.00 0 |__java
07:33:39 PM - 3088 0.00 0.00 0.00 0.00 0 |__java
07:33:39 PM - 3089 0.00 0.00 0.00 0.00 0 |__java
07:33:39 PM - 3090 0.00 0.00 0.00 0.00 0 |__java
07:33:39 PM - 3091 0.00 0.00 0.00 0.00 0 |__java
07:33:39 PM - 3092 0.00 0.00 0.00 0.00 0 |__java
07:33:39 PM - 3093 0.00 0.00 0.00 0.00 0 |__java
07:33:39 PM - [color=red][b]3094 97.00 0.00 0.00 97.00 0 |__java[/b][/color]

07:33:39 PM TGID TID %usr %system %guest %CPU CPU Command
07:33:40 PM 3085 - 99.00 0.00 0.00 99.00 0 java
07:33:40 PM - 3085 0.00 0.00 0.00 0.00 0 |__java
07:33:40 PM - 3086 0.00 0.00 0.00 0.00 0 |__java
07:33:40 PM - 3087 0.00 0.00 0.00 0.00 0 |__java
07:33:40 PM - 3088 0.00 0.00 0.00 0.00 0 |__java
07:33:40 PM - 3089 0.00 0.00 0.00 0.00 0 |__java
07:33:40 PM - 3090 0.00 0.00 0.00 0.00 0 |__java
07:33:40 PM - 3091 0.00 0.00 0.00 0.00 0 |__java
07:33:40 PM - 3092 0.00 0.00 0.00 0.00 0 |__java
07:33:40 PM - 3093 0.00 0.00 0.00 0.00 0 |__java
07:33:40 PM - 3094 98.00 0.00 0.00 98.00 0 |__java


从结果中看出,线程3094占用的CPU资源很多。
3)导出线程转储。
jstack -l 3085 > /root/tmp/jstack.log
4)查找消耗最高的线程
将jstack.log下载到本地,将线程id 3094转换为16进制为c16,从jstack.log查找nid=oxc16,找到内容如下
"Thread-0" prio=10 tid=0xb6d46c00 nid=0xc16 runnable [0xb423b000]
java.lang.Thread.State: RUNNABLE
at HoldCPUMain$HoldCPUTask.run(HoldCPUMain.java:5)
at java.lang.Thread.run(Thread.java:619)

从中可以看出该线程在执行HoldCPUTask.run方法。
7.sy高代码示例
代码:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;


public class HoldIOMain {
public static class HoldIOTask implements Runnable{
public void run(){
while(true){
try{
FileOutputStream fos = new FileOutputStream(new File("tmp"));
for(int i=0;i<10000;i++)
fos.write(i);
fos.close();
FileInputStream fis = new FileInputStream(new File("tmp"));
while(fis.read() != -1);
}catch(Exception e){
e.printStackTrace();
}
}
}
}
public static class LazyTask implements Runnable{
public void run(){
try{
Thread.sleep(1000);
}catch(Exception e){}
}
}
public static void main(String[] args) throws Exception{
new Thread(new HoldIOTask()).start();
new Thread(new LazyTask()).start();
new Thread(new LazyTask()).start();
new Thread(new LazyTask()).start();
}
}


运行结果:
top - 19:48:39 up 26 min, 4 users, load average: 58.63, 177.84, 102.98
Tasks: 151 total, 1 running, 150 sleeping, 0 stopped, 0 zombie
Cpu(s): 21.2%us, [color=red][b]73.8%sy[/b][/color], 0.0%ni, 0.0%id, 5.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 1309844k total, 1027412k used, 282432k free, 56296k buffers
Swap: 4161528k total, 0k used, 4161528k free, 255160k cached

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4552 root 20 0 407m 11m 7108 S 95.1 0.9 0:12.72 java

改代码进行了大量的IO操作,由于进行IO操作需要调用操作系统来完成,所以导致了%sy很高。

8.cs高代码示例
代码:
import java.util.Random;


public class SyHighDemo {
private static int threadCount = 500;
public static void main(String[] args) throws Exception{
if(args.length == 1){
threadCount = Integer.parseInt(args[0]);
}
SyHighDemo demo = new SyHighDemo();
demo.runTest();
}
private Random random = new Random();
private Object[] locks;
private void runTest() throws Exception{
locks = new Object[threadCount];
for(int i=0;i<threadCount;i++){
locks[i] = new Object();
}
for(int i=0;i<threadCount;i++){
new Thread(new ATask(i)).start();
new Thread(new BTask(i)).start();
}
}
class ATask implements Runnable{
private Object lockObject = null;
public ATask(int i){
lockObject = locks[i];
}
public void run(){
while(true){
try{
synchronized (lockObject) {
lockObject.wait(random.nextInt(10));
}
}catch(Exception e){
;
}
}
}
}
class BTask implements Runnable{
private Object lockObject = null;
public BTask(int i){
lockObject = locks[i];
}
public void run(){
while(true){
synchronized (lockObject) {
lockObject.notifyAll();
}
try{
Thread.sleep(random.nextInt(5));
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
}


运行结果:
# top
top - 19:40:38 up 18 min, 4 users, load average: 64.81, 16.87, 6.10
Tasks: 151 total, 1 running, 150 sleeping, 0 stopped, 0 zombie
Cpu(s): 36.1%us, [color=red][b]63.9%sy,[/b][/color] 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 1309844k total, 1047484k used, 262360k free, 55936k buffers
Swap: 4161528k total, 0k used, 4161528k free, 255112k cached

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
3384 root 20 0 577m 25m 7148 S 99.9 2.0 0:39.24 java

由于ATask和BTask频繁的进行线程切换,所以导致%sy CPU占用很高,同时导致cs值也很大。


运行结果如下:
# vmstat 2 3
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----
r b swpd free buff cache si so bi bo in [color=red][b]cs [/b][/color]us sy id wa st
613 0 0 242864 56180 255152 65 0 0 217 1170 [color=red][b]20935 [/b][/color]64 18 7 10 0
561 0 0 242848 56180 255152 24 0 0 0 2079 [b][color=red]122750 [/color][/b]41 59 0 0 0
581 0 0 242832 56180 255152 0 0 0 0 2095 [color=red][b]121878 [/b][/color]41 59 0 0 0


9.参考资料
《Java性能优化权威指南》
葛一鸣:《Java性能优化》
林昊:《分布式Java应用:基础与实践》
http://www.cnblogs.com/net2012/archive/2013/01/18/2866907.html
http://blog.itpub.net/8554499/viewspace-600478
http://www.7dtest.com/site/thread-5904-1-1.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值