文档章节

jvm原理和优化

罗文浩
 罗文浩
发布于 2016/09/09 10:55
字数 3450
阅读 80
收藏 1

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

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

一、CPU过高。

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

 

深入理解JVM性能调优

深入理解JVM性能调优

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

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

 

深入理解JVM性能调优

 在

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

 

深入理解JVM性能调优

 我们也可以通过

sysstat 工具集的 pidstat 来查看

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

安装方法:

1、chmod +x configure

2、./configure

3、make

4、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性能调优

 二、文件

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性能调优

 占用了大量的内存资源。

三、内存消耗

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

对于内存消耗,还有一部分是直接物理内存的,不在堆空间,前面我们也写过对应的示例。之前一个系统就是因为有大量的NIO操作,而NIO是使用物理内存的,并且开辟的物理内存是在触发FULL GC的时候才进行回收的,但是当时的机器总内存为16GB 给堆的内存是14GB Edon为1.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程序锁竞争过于激烈,比如你只有2颗CPU,但是你启用了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、  使用缓存

© 著作权归作者所有

罗文浩
粉丝 21
博文 138
码字总数 223451
作品 0
海淀
架构师
私信 提问
Java面试无非也就这几个知识点,大家是否都掌握了

Java语言的关键点 掌握静态方法和属性 重视接口 学好集合框架 例外捕捉 多线程需要理解机理(多线程原理和多线程安全) 了解网络编程 不需要精通,掌握以下知识点,面试基本没有问题。 这里没有...

土豆宝
2016/08/22
9.4K
37
第7课:Java Spring Boot 2.0安全机制、漏洞与MVC身份验证实战

《阿里巴巴Java Spring Boot 2.0开发实战课程》07课 本期分享专家:徐雷—阿里巴巴特邀Java讲师,MongoDB讲师 本期分享主题:Java Spring Boot2.0实战MyBatis与优化 (Java面试题) Java Spri...

徐雷frank
2018/12/12
0
0
[Java学习探讨]为什么学Java虚拟机的Java程序员更值钱?

[Java学习探讨]为什么学Java虚拟机的Java程序员更值钱? 曾经的我经常害怕处理与JVM相关的异常,对JVM的配置参数也一无所知,那时候我天真地认为,JVM的出现本身就是想让程序员屏蔽实现细节,...

原创小博客
2018/07/19
0
0
java两年开发经验,北京,本科,有java培训经历,求推荐工作

1,大学信息与计算科学专业,并在java培训机构培训过,深刻理解面向对象 2,熟练使用js,extjs,jquery,ajax,div+css等前端技术 3,熟练使用ssh框架,并亲自实现过其中的ioc,aop,or—map...

小虫大人
2014/03/04
621
7
Java开发:错过金三银四 你还要错过金九银十吗?面试大纲总结

前言: 一年之计在于春 金三银四已过,2018也已经年过一半多,作为一个开发人员,你是否面上了自己理想的公司,薪资达到心中理想的高度? 面试:如果不准备充分的面试,完全是浪费时间,更是...

Java大蜗牛
2018/08/16
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Bash 和 Python 编程语言优缺点分析

Bash 和 Python 是大多数自动化工程师最喜欢的编程语言。它们都各有优缺点,有时很难选择应该使用哪一个。所以,最诚实的答案是:这取决于任务、范围、背景和任务的复杂性。 让我们来比较一下...

xiangyunyan
24分钟前
3
0
Kubernetes从懵圈到熟练:读懂这一篇,集群节点不下线

排查完全陌生的问题,完全不熟悉的系统组件,是售后工程师的一大工作乐趣,当然也是挑战。今天借这篇文章,跟大家分析一例这样的问题。排查过程中,需要理解一些自己完全陌生的组件,比如sys...

阿里云云栖社区
30分钟前
7
0
解决exe4打包出现的问题

https://blog.csdn.net/gem_yaorao/article/details/48626155

南桥北木
46分钟前
1
0
SpringBoot高级篇JdbcTemplate之数据更新与删除

前面介绍了JdbcTemplate的插入数据和查询数据,占用CURD中的两项,本文则将主要介绍数据更新和删除。从基本使用上来看,姿势和前面的没啥两样 <!-- more --> I. 环境准备 环境依然借助前面一...

小灰灰Blog
今天
3
0
Filecoin 编译问题

https://github.com/filecoin-project/go-filecoin/issues/2503 Error go run ./build build command from root I've faced on this error Building go-filecoin...git log -n 1 --forma......

怎当她临去时秋波那一转
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部