在上文中咱们分析了不少性能监控工具,介绍这些工具的目的只有一个,那就是找出对应的性能瓶颈。盲目的性能调优是没有效果的,只有充分知道了哪里出了问题,针对性的结果才是立竿见影的。解决了主要的性能问题,那些次要的性能问题也就不足为虑了!html
咱们知道,性能问题无非就这么几种:CPU、内存、磁盘IO、网络。那咱们来逐一介绍如下相关的现象和一些可能出现的问题。java
1、CPU太高。ios
查看CPU最简单的咱们使用任务管理器查看,以下图所示,windows下使用任务管理器查看,Linux下使用top查看。web
通常咱们的服务器都采用Linux,所以咱们重点关注一下Linux(注:windows模式下相信你们已经很熟悉了,而且前面咱们已经提到,使用资源监视器能够很清楚的看到系统的各项参数,在这里我就很少作介绍了)算法
在top视图下,对于多核的CPU,显示的CPU资源有可能超过100%,由于这里显示的是全部CPU占用百分百的总和,若是你须要看单个CPU的占用状况,直接按键1就能够看到。以下图所示,个人一台测试机为8核16GB内存。sql
在数据库
top 视图下,按键 shift+h 后,会显示各个线程的 CPU 资源消耗状况,以下图所示:windows
咱们也能够经过缓存
sysstat 工具集的 pidstat 来查看服务器
注:sysstat下载地址:http://sebastien.godard.pagesperso-orange.fr/download.html
安装方法:
一、chmod +x configure
二、./configure
三、make
四、make install
如输入pidstat 1 2就会隔一秒在控制台输出一次固然CPU的状况,共输出2次
除了
top 、 pidstat 之外, vmstat 也能够进行采样分析
相关
top 、 pidstat 、 mstat 的用法你们能够去网上查找。
下面咱们主要来介绍如下当出现CPU太高的时候,或者CPU不正常的时候,咱们该如何去处理?
CPU消耗太高主要分为用户进程占用CPU太高和内核进程占用CPU太高(在Linux下top视图下us指的是用户进程,而sy是指内核进程),咱们来看一个案例:
程序运行前,系统运行平稳,其中蓝色的线表示总的
CPU 利用率,而红色的线条表示内核使用率。部署 war 测试程序,运行以下图所示:
对于一个
web 程序,尚未任何请求就占用这么多 CPU 资源,显然是不正常的。而且咱们看到,不是系统内核占用的大量 CPU ,而是系统进程,那是哪个进程的呢?咱们来看一下。
很明显是咱们的
java 进程,那是那个地方致使的呢?这就须要用到咱们以前提到的性能监控工具。在此咱们使用可视化监控工具 VisualVM。
首先咱们排除了是
GC 过于频繁而致使大 CPU 太高,由于很明显监控视图上没有 GC 的活动。而后咱们打开 profilter 去查看如下,是那个线程致使了 CPU 的太高?
前面一些线程都是容器使用的,而下面一个线程也一直在执行,那是什么地方调用的呢?查找代码中使用
ThredPoolExecutor 的地方。终于发现如下代码。
private BlockingQueue queue;
private Executor executor;
//……
public void run() {
while(true){
try {
SendMsg sendMsg = queue.poll();//从队列中取出
if(null != sendMsg) {
sendForQueue(sendMsg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
问题很显然了,咱们看一下对应BlockingQueue的poll方法的API文档。
不难理解了,虽然使用了阻塞的队列,可是使用了非阻塞的取法,当数据为空时直接返回
null ,那这个语句就等价于下面的语句。
@Override
public void run() {
while(true){
}
}
至关于死循环么,很显然是很是耗费CPU资源的,而且咱们还能够发现这样的死循环是耗费的单颗CPU资源,所以能够解释上图为啥有一颗CPU占用特别高。咱们来看一下部署在Linux下的top视图。
猛一看,不是很高么?咱们按键
1 来看每一个单独 CPU 的状况!
这下看的很清楚了吧!明显一颗
CPU 被跑满了。(由于一个单独的死循环只能用到一颗 CPU ,都是单线程运行的)。
问题找到,立刻修复代码为阻塞时存取,以下所示:
@Override
public void run() {
while(true){
try {
SendMsg sendMsg = queue.take();//从队列中取出
sendForQueue(sendMsg);
} catch (Exception e) {
e.printStackTrace();
}
}
}
再来监控
CPU 的变换,咱们能够看到,基本上不消耗 CPU 资源(是我没作任何的访问哦,有用户创建线程就会消耗)。
再来看
java 进程的消耗,基本上不消耗 CPU 资源
再来看VisualVM的监控,咱们就能够看到基本上都是容器的一些线程了
以上示例展现了
CPU 消耗太高状况下用户线程占用特别高的状况。也就是 Linux 下 top 视图中 us 比较高的状况。发生这种状况的缘由主要有如下几种:程序不停的在执行无阻塞的循环、正则或者纯粹的数学运算、 GC 特别频繁。
CPU太高还有一种状况是内核占用CPU很高。咱们来看另一个示例。
package com.yhj.jvm.monitor.cpu.sy;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Described:系统内核占用CPU太高测试用例
* @author YHJ create at 2012-3-28 下午05:27:33
* @FileNmae com.yhj.jvm.monitor.cpu.sy.SY_Hign_TestCase.java
*/
public class SY_Hign_TestCase {
private final static int LOCK_COUNT = 1000;
//默认初始化LOCK_COUNT个锁对象
private Object [] locks = new Object[LOCK_COUNT];
private Random random = new Random();
//构造时初始化对应的锁对象
public SY_Hign_TestCase() {
for(int i=0;i<LOCK_COUNT;++i)
locks[i]=new Object();
}
abstract class Task implements Runnable{
protected Object lock;
public Task(int index) {
this.lock= locks[index];
}
@Override
public void run() {
while(true){ //循环执行本身要作的事情
doSth();
}
}
//作类本身要作的事情
public abstract void doSth();
}
//任务A 休眠本身的锁
class TaskA extends Task{
public TaskA(int index) {
super(index);
}
@Override
public void doSth() {
synchronized (lock) {
try {
lock.wait(random.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//任务B 唤醒全部锁
class TaskB extends Task{
public TaskB(int index) {
super(index);
}
@Override
public void doSth() {
try {
synchronized (lock) {
lock.notifyAll();
Thread.sleep(random.nextInt(10));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//启动函数
public void start(){
ExecutorService service = Executors.newCachedThreadPool();
for(int i=0;i<LOCK_COUNT;++i){
service.execute(new TaskA(i));
service.execute(new TaskB(i));
}
}
//主函数入口
public static void main(String[] args) {
new SY_Hign_TestCase().start();
}
}
代码很简单,就是建立了2000个线程,让必定的线程去等待,另一个线程去释放这些资源,这样就会有大量的线程切换,咱们来看下效果。
很明显,
CPU 的内核占用率很高,咱们拿具体的资源监视器看一下:
很明显能够看出有不少线程切换占用了大量的
CPU 资源。
一样的程序部署在Linux下,top视图以下图所示:
展开对应的
CPU 资源,咱们能够清晰的看到以下情形:
你们能够看到有大量的
sy 内核占用,可是也有很多的 us , us 是由于咱们启用了大量的循环,而 sy 是由于大量线程切换致使的。
咱们也可使用vmstat来查看,以下图所示:
2、文件
IO 消耗过大,磁盘队列高。
在windows环境下,咱们可使用资源监视器查看对应的IO消耗,以下图所示:
这里不但能够看到当前磁盘的负载信息,队列详情,还能看到每一个单独的进程的资源消耗状况。
Linux下主要使用pidstat、iostat等进行分析。以下图所示
Pidstat –d –t –p [pid] {time} {count}
如:pidstat -d -t -p 18720 1 1
Iostat
Iostat –x xvda 1 10作定时采样
废话很少说,直接来示例,上干货!
package com.yhj.jvm.monitor.io;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Described:IO测试用例
* @author YHJ create at 2012-3-29 上午09:56:06
* @FileNmae com.yhj.jvm.monitor.io.IO_TestCase.java
*/
public class IO_TestCase {
private String fileNmae = "monitor.log";
private String context ;
// 和CPU处理器个数相同,既充分利用CPU资源,又致使线程频繁切换
private final static int THRED_COUNT = Runtime.getRuntime().availableProcessors();
public IO_TestCase() {//加长写文件的内容,拉长每次写入的时间
StringBuilder sb = new StringBuilder();
for(int i=0;i<1000;++i){
sb.append("context index :")
.append(i)
.append("\n");
this.context= new String(sb);
}
}
//写文件任务
class Task implements Runnable{
@Override
public void run() {
while(true){
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new FileWriter(fileNmae,true));//追加模式
writer.write(context);
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
//启动函数
public void start(){
ExecutorService service = Executors.newCachedThreadPool();
for(int i=0;i<THRED_COUNT;++i)
service.execute(new Task());
}
//主函数入口
public static void main(String[] args) {
new IO_TestCase().start();
}
}
这段示例很简单,经过建立一个和CPU个数相同的线程池,而后开启这么多线程一块儿读写同一个文件,这样就会因IO资源的竞争而致使IO的队列很高,以下图所示:
关掉以后立刻就下来了
咱们把这个部署到
Linux 上观看。
这里的
%idle 指的是系统没有完成写入的数量占用 IO 总量的百分百,为何这么高咱们的系统还能承受?由于我这台机器的内存为16GB 的,咱们来查看如下 top 视图就能够清晰的看到。
占用了大量的内存资源。
3、内存消耗
对于JVM的内存模型你们已经很清楚了,前面咱们讲了JVM的性能监控工具。对于Java应用来讲,出现问题主要消耗在于JVM的内存上,而JVM的内存,JDK已经给咱们提供了不少的工具。在实际的生成环境,大部分应用会将-Xms和-Xmx设置为相同的,避免运行期间不断开辟内存。
对于内存消耗,还有一部分是直接物理内存的,不在堆空间,前面咱们也写过对应的示例。以前一个系统就是由于有大量的NIO操做,而NIO是使用物理内存的,而且开辟的物理内存是在触发FULL GC的时候才进行回收的,可是当时的机器总内存为16GB 给堆的内存是14GB Edon为1.5GB,也就是实际剩下给物理呢哦村的只有0.5GB,最终致使老是发生内存溢出,但监控堆、栈的内存消耗都不大。在这里我就很少写了!
4、网络消耗过大
Windows下使用本地网络视图能够监控当前的网络流量大小
更详细的资料能够打开资源监视器,以下图所示
Linux
平台可使用如下 sar 命令查看
sar -n DEV 1 2
字段说明:
rxpck/s:每秒钟接收的数据包
txpck/s:每秒钟发送的数据包
rxbyt/s:每秒钟接收的字节数
txbyt/s:每秒钟发送的字节数
rxcmp/s:每秒钟接收的压缩数据包
txcmp/s:每秒钟发送的压缩数据包
rxmcst/s:每秒钟接收的多播数据包
Java程序通常不会出现网络IO致使问题,所以在这里也不过的的阐述。
5、程序执行缓慢
当CPU、内存、磁盘、网络都不高,程序仍是执行缓慢的话,可能引起的缘由大体有如下几种:
1程序锁竞争过于激烈,好比你只有2颗CPU,可是你启用了200个线程,就会致使大量的线程等待和切换,而这不会致使CPU很高,可是不少线程等待意味着你的程序运行很慢。
2未充分利用硬件资源。好比你的机器是16个核心的,可是你的程序是单线程运行的,即便你的程序优化的很好,当须要处理的资源比较多的时候,程序还会很慢,所以如今都在提倡分布式,经过大量廉价的PC机来提高程序的执行速度!
3其余服务器反应缓慢,如数据库、缓存等。当大量作了分布式,程序CPU负载都很低,可是提交给数据库的sql没法很快执行,也会特别慢。
总结一下,当出现性能问题的时候咱们该怎么作?
1、CPU太高
一、 us太高
使用监控工具快读定位哪里有死循环,大计算,对于死循环经过阻塞式队列解决,对于大计算,建议分配单独的机器作后台计算,尽可能不要影响用户交互,若是必定要的话(如框计算、云计算),只能经过大量分布式来实现
二、 sy太高
最有效的方法就是减小进程,不是进程越多效率越高,通常来讲线程数和CPU的核心数相同,这样既不会形成线程切换,又不会浪费CPU资源
2、内存消耗太高
一、 及时释放没必要要的对象
二、 使用对象缓存池缓冲
三、 采用合理的缓存失效算法(还记得咱们以前提到的弱引用、幽灵引用么?)
3、磁盘IO太高
一、 异步读写文件
二、 批量读写文件
三、 使用缓存技术
四、 采用合理的文件读写规则
4、网络
一、增长宽带流量
5、资源消耗很少但程序运行缓慢
一、使用并发包,减小锁竞争
二、对于必须单线程执行的使用队列处理
三、大量分布式处理
6、未充分利用硬件资源
一、 修改程序代码,使用多线程处理
二、 修正外部资源瓶颈,作业务拆分
三、 使用缓存