jvm原理和优化

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

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

1、CPU太高。ios

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

 

深刻理解JVM性能调优

深刻理解JVM性能调优

通常咱们的服务器都采用Linux,所以咱们重点关注一下Linux(注:windows模式下相信你们已经很熟悉了,而且前面咱们已经提到,使用资源监视器能够很清楚的看到系统的各项参数,在这里我就很少作介绍了)算法

在top视图下,对于多核的CPU,显示的CPU资源有可能超过100%,由于这里显示的是全部CPU占用百分百的总和,若是你须要看单个CPU的占用状况,直接按键1就能够看到。以下图所示,个人一台测试机为8核16GB内存。sql

 

深刻理解JVM性能调优

 在数据库

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

 

深刻理解JVM性能调优

 咱们也能够经过缓存

sysstat 工具集的 pidstat 来查看服务器

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

安装方法:

一、chmod +x configure

二、./configure

三、make

四、make install

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

 

深刻理解JVM性能调优

 除了

top 、 pidstat 之外, vmstat 也能够进行采样分析

 

深刻理解JVM性能调优

 相关

top 、 pidstat 、 mstat 的用法你们能够去网上查找。

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

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

 

深刻理解JVM性能调优

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

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

 

深刻理解JVM性能调优

 对于一个

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

 

深刻理解JVM性能调优

 很明显是咱们的

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

 

深刻理解JVM性能调优

 首先咱们排除了是

GC 过于频繁而致使大 CPU 太高,由于很明显监控视图上没有 GC 的活动。而后咱们打开 profilter 去查看如下,是那个线程致使了 CPU 的太高?

 

深刻理解JVM性能调优

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

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文档。

 

深刻理解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 消耗太高状况下用户线程占用特别高的状况。也就是 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个线程,让必定的线程去等待,另一个线程去释放这些资源,这样就会有大量的线程切换,咱们来看下效果。

 

深刻理解JVM性能调优

 很明显,

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

 

深刻理解JVM性能调优

 很明显能够看出有不少线程切换占用了大量的

CPU 资源。

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

 

深刻理解JVM性能调优

 展开对应的

CPU 资源,咱们能够清晰的看到以下情形:

 

深刻理解JVM性能调优

 你们能够看到有大量的

sy 内核占用,可是也有很多的 us , us 是由于咱们启用了大量的循环,而 sy 是由于大量线程切换致使的。

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

 

深刻理解JVM性能调优

 2、文件

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

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

 

深刻理解JVM性能调优

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

Linux下主要使用pidstat、iostat等进行分析。以下图所示

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;

 

/**

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

 

深刻理解JVM性能调优

 关掉以后立刻就下来了

 

深刻理解JVM性能调优

 咱们把这个部署到

Linux 上观看。

 

深刻理解JVM性能调优

 这里的

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

 

深刻理解JVM性能调优

 占用了大量的内存资源。

3、内存消耗

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

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

4、网络消耗过大

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致使问题,所以在这里也不过的的阐述。

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、未充分利用硬件资源

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

二、  修正外部资源瓶颈,作业务拆分

三、  使用缓存

相关文章
相关标签/搜索