深入理解JVM—性能调优

来源:互联网 发布:js格式视频 编辑:程序博客网 时间:2024/05/01 02:01

 在上文中我们分析了很多性能监控工具,介绍这些工具的目的只有一个,那就是找出对应的性能瓶颈。盲目的性能调优是没有效果的,只有充分知道了哪里出了问题,针对性的结果才是立竿见影的。解决了主要的性能问题,那些次要的性能问题也就不足为虑了!

我们知道,性能问题无非就这么几种:CPU、内存、磁盘IO、网络。那我们来逐一介绍以下相关的现象和一些可能出现的问题。

一、CPU过高。

查看CPU最简单的我们使用任务管理器查看,如下图所示,windows下使用任务管理器查看,Linux下使用top查看。

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰
深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

一般我们的服务器都采用Linux,因此我们重点关注一下Linux(注:windows模式下相信大家已经很熟悉了,并且前面我们已经提到,使用资源监视器可以很清楚的看到系统的各项参数,在这里我就不多做介绍了)

top视图下,对于多核的CPU,显示的CPU资源有可能超过100%,因为这里显示的是所有CPU占用百分百的总和,如果你需要看单个CPU的占用情况,直接按键1就可以看到。如下图所示,我的一台测试机为816GB内存。

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 在

top视图下,按键shift+h后,会显示各个线程的CPU资源消耗情况,如下图所示:

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 我们也可以通过

sysstat工具集的pidstat来查看

注:sysstat下载地址:http://sebastien.godard.pagesperso-orange.fr/download.html

安装方法:

1chmod +x configure

2./configure

3make

4make install

如输入pidstat 1 2就会隔一秒在控制台输出一次当然CPU的情况,共输出2

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 除了

toppidstat以外,vmstat也可以进行采样分析

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 相关

toppidstatmstat的用法大家可以去网上查找。

下面我们主要来介绍以下当出现CPU过高的时候,或者CPU不正常的时候,我们该如何去处理?

CPU消耗过高主要分为用户进程占用CPU过高和内核进程占用CPU过高(在Linuxtop视图下us指的是用户进程,而sy是指内核进程),我们来看一个案例:

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 程序运行前,系统运行平稳,其中蓝色的线表示总的

CPU利用率,而红色的线条表示内核使用率。部署war测试程序,运行如下图所示:

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 对于一个

web程序,还没有任何请求就占用这么多CPU资源,显然是不正常的。并且我们看到,不是系统内核占用的大量CPU,而是系统进程,那是哪一个进程的呢?我们来看一下。

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 很明显是我们的

java进程,那是那个地方导致的呢?这就需要用到我们之前提到的性能监控工具。在此我们使用可视化监控工具VisualVM

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 首先我们排除了是

GC过于频繁而导致大CPU过高,因为很明显监控视图上没有GC的活动。然后我们打开profilter去查看以下,是那个线程导致了CPU的过高?

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 前面一些线程都是容器使用的,而下面一个线程也一直在执行,那是什么地方调用的呢?查找代码中使用

ThredPoolExecutor的地方。终于发现以下代码。

    private BlockingQueue<SendMsg> queue;

    private Executor executor;

//……

public void run() {

        while(true){

           try {

              SendMsg sendMsg = queue.poll();//从队列中取出

              if(null != sendMsg) {

                  sendForQueue(sendMsg);

              }

           } catch (Exception e) {

              e.printStackTrace();

           }

       }

    }

问题很显然了,我们看一下对应BlockingQueuepoll方法的API文档。

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 不难理解了,虽然使用了阻塞的队列,但是使用了非阻塞的取法,当数据为空时直接返回

null,那这个语句就等价于下面的语句。

@Override

    public void run() {

       while(true){

          

       }

    }

相当于死循环么,很显然是非常耗费CPU资源的,并且我们还可以发现这样的死循环是耗费的单颗CPU资源,因此可以解释上图为啥有一颗CPU占用特别高。我们来看一下部署在Linux下的top视图。

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 猛一看,不是很高么?我们按键

1来看每个单独CPU的情况!

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 这下看的很清楚了吧!明显一颗

CPU被跑满了。(因为一个单独的死循环只能用到一颗CPU,都是单线程运行的)。

问题找到,马上修复代码为阻塞时存取,如下所示:

@Override

    public void run() {

       while(true){

           try {

              SendMsg sendMsg = queue.take();//从队列中取出

              sendForQueue(sendMsg);

           } catch (Exception e) {

              e.printStackTrace();

           }

       }

    }

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 再来监控

CPU的变换,我们可以看到,基本上不消耗CPU资源(是我没做任何的访问哦,有用户建立线程就会消耗)。

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 再来看

java进程的消耗,基本上不消耗CPU资源

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

再来看VisualVM的监控,我们就可以看到基本上都是容器的一些线程了

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 以上示例展示了

CPU消耗过高情况下用户线程占用特别高的情况。也就是Linuxtop视图中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.locklocks[index];

       }

       @Override

       public void run() {

           while(true){  //循环执行自己要做的事情

              doSth();

           }

       }

       //做类自己要做的事情

       public abstract void doSth();

    }

 

    //任务休眠自己的锁

    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();

              }

           }

       }

 

    }

 

    //任务唤醒所有锁

    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个线程,让一定的线程去等待,另外一个线程去释放这些资源,这样就会有大量的线程切换,我们来看下效果。

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 很明显,

CPU的内核占用率很高,我们拿具体的资源监视器看一下:

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 很明显可以看出有很多线程切换占用了大量的

CPU资源。

同样的程序部署在Linux下,top视图如下图所示:

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 展开对应的

CPU资源,我们可以清晰的看到如下情形:

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 大家可以看到有大量的

sy内核占用,但是也有不少的usus是因为我们启用了大量的循环,而sy是因为大量线程切换导致的。

我们也可以使用vmstat来查看,如下图所示:

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 二、文件

IO消耗过大,磁盘队列高。

windows环境下,我们可以使用资源监视器查看对应的IO消耗,如下图所示:

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 这里不但可以看到当前磁盘的负载信息,队列详情,还能看到每个单独的进程的资源消耗情况。

Linux下主要使用pidstatiostat等进行分析。如下图所示

Pidstat –d –t –p [pid] {time} {count}

如:pidstat -d -t -p 18720 1 1

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰
深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

Iostat

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰 

Iostat –x xvda 1 10做定时采样

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 废话不多说,直接来示例,上干货!

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;

 

/**

 * @DescribedIO测试用例

 * @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.contextnew 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的队列很高,如下图所示:

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 关掉之后马上就下来了

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 我们把这个部署到

Linux上观看。

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 这里的

%idle指的是系统没有完成写入的数量占用IO总量的百分百,为什么这么高我们的系统还能承受?因为我这台机器的内存为16GB的,我们来查看以下top视图就可以清晰的看到。

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 占用了大量的内存资源。

三、内存消耗

对于JVM的内存模型大家已经很清楚了,前面我们讲了JVM的性能监控工具。对于Java应用来说,出现问题主要消耗在于JVM的内存上,而JVM的内存,JDK已经给我们提供了很多的工具。在实际的生成环境,大部分应用会将-Xms-Xmx设置为相同的,避免运行期间不断开辟内存。

对于内存消耗,还有一部分是直接物理内存的,不在堆空间,前面我们也写过对应的示例。之前一个系统就是因为有大量的NIO操作,而NIO是使用物理内存的,并且开辟的物理内存是在触发FULL GC的时候才进行回收的,但是当时的机器总内存为16GB 给堆的内存是14GB Edon1.5GB,也就是实际剩下给物理呢哦村的只有0.5GB,最终导致总是发生内存溢出,但监控堆、栈的内存消耗都不大。在这里我就不多写了!

四、网络消耗过大

Windows下使用本地网络视图可以监控当前的网络流量大小

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 更详细的资料可以打开资源监视器,如下图所示

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 Linux

平台可以使用以下sar命令查看

sar -n DEV 1 2

深入理解JVM—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

 字段说明:

rxpck/s:每秒钟接收的数据包

txpck/s:每秒钟发送的数据包

rxbyt/s:每秒钟接收的字节数

txbyt/s:每秒钟发送的字节数

rxcmp/s:每秒钟接收的压缩数据包

txcmp/s:每秒钟发送的压缩数据包

rxmcst/s:每秒钟接收的多播数据包

Java程序一般不会出现网络IO导致问题,因此在这里也不过的的阐述。

五、程序执行缓慢

CPU、内存、磁盘、网络都不高,程序还是执行缓慢的话,可能引发的原因大致有以下几种:

1程序锁竞争过于激烈,比如你只有2CPU,但是你启用了200个线程,就会导致大量的线程等待和切换,而这不会导致CPU很高,但是很多线程等待意味着你的程序运行很慢。

2未充分利用硬件资源。比如你的机器是16个核心的,但是你的程序是单线程运行的,即使你的程序优化的很好,当需要处理的资源比较多的时候,程序还会很慢,因此现在都在提倡分布式,通过大量廉价的PC机来提升程序的执行速度!

3其他服务器反应缓慢,如数据库、缓存等。当大量做了分布式,程序CPU负载都很低,但是提交给数据库的sql无法很快执行,也会特别慢。

总结一下,当出现性能问题的时候我们该怎么做?

一、CPU过高

1、  us过高

使用监控工具快读定位哪里有死循环,大计算,对于死循环通过阻塞式队列解决,对于大计算,建议分配单独的机器做后台计算,尽量不要影响用户交互,如果一定要的话(如框计算、云计算),只能通过大量分布式来实现

2、  sy过高

最有效的方法就是减少进程,不是进程越多效率越高,一般来说线程数和CPU的核心数相同,这样既不会造成线程切换,又不会浪费CPU资源

二、内存消耗过高

1、  及时释放不必要的对象

2、  使用对象缓存池缓冲

3、  采用合理的缓存失效算法(还记得我们之前提到的弱引用、幽灵引用么?)

三、磁盘IO过高

1、  异步读写文件

2、  批量读写文件

3、  使用缓存技术

4、  采用合理的文件读写规则

四、网络

1、增加宽带流量

五、资源消耗不多但程序运行缓慢

1、使用并发包,减少锁竞争

2、对于必须单线程执行的使用队列处理

3、大量分布式处理

六、未充分利用硬件资源

1、  修改程序代码,使用多线程处理

2、  修正外部资源瓶颈,做业务拆分

3、  使用缓存

0 0
原创粉丝点击