zoukankan      html  css  js  c++  java
  • 深入理解JVM—性能调优

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

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

    一、CPU过高。

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

    一般我们的服务器都采用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是指内核进程),我们来看一个案例:

    程序运行前,系统运行平稳,其中蓝色的线表示总的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.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个线程,让一定的线程去等待,另外一个线程去释放这些资源,这样就会有大量的线程切换,我们来看下效果。深入理解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—性能调优 - 一线天色 天宇星辰 - 一线天色 天宇星辰

    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;

     

    /**

     * @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(" ");

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

    深入理解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、  使用缓存

  • 相关阅读:
    .sln是什么的格式
    VMware的四种网络连接方式
    Cisco 2950交换机 配置手册
    Framework2.0标识没有写访问权限的解决办法
    ASP.NET IIS 注册工具
    .suo介绍
    Uri In WPF
    BindingErrorListener In WPF
    WPF 详解模板
    .NET装饰器(Decorator)模式
  • 原文地址:https://www.cnblogs.com/PerOpt/p/3748889.html
Copyright © 2011-2022 走看看