深刻理解JVM—性能調優

在上文中咱們分析了不少性能監控工具,介紹這些工具的目的只有一個,那就是找出對應的性能瓶頸。盲目的性能調優是沒有效果的,只有充分知道了哪裏出了問題,針對性的結果纔是立竿見影的。解決了主要的性能問題,那些次要的性能問題也就不足爲慮了!html

咱們知道,性能問題無非就這麼幾種:CPU、內存、磁盤IO、網絡。那咱們來逐一介紹如下相關的現象和一些可能出現的問題。java

1、CPU太高。ios

查看CPU最簡單的咱們使用任務管理器查看,以下圖所示,windows下使用任務管理器查看,Linux下使用top查看。web

通常咱們的服務器都採用Linux,所以咱們重點關注一下Linux(注:windows模式下相信你們已經很熟悉了,而且前面咱們已經提到,使用資源監視器能夠很清楚的看到系統的各項參數,在這裏我就很少作介紹了)算法

top視圖下,對於多核的CPU,顯示的CPU資源有可能超過100%,由於這裏顯示的是全部CPU佔用百分百的總和,若是你須要看單個CPU的佔用狀況,直接按鍵1就能夠看到。以下圖所示,個人一臺測試機爲816GB內存。sql

深刻理解JVM—性能調優 - 一線天色 天宇星辰 - 一線天色 天宇星辰

top視圖下,按鍵shift+h後,會顯示各個線程的CPU資源消耗狀況,以下圖所示:數據庫

深刻理解JVM—性能調優 - 一線天色 天宇星辰 - 一線天色 天宇星辰

咱們也能夠經過sysstat工具集的pidstat來查看windows

注: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—性能調優 - 一線天色 天宇星辰 - 一線天色 天宇星辰

2、文件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("\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 Edon1.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程序鎖競爭過於激烈,好比你只有2CPU,可是你啓用了200個線程,就會致使大量的線程等待和切換,而這不會致使CPU很高,可是不少線程等待意味着你的程序運行很慢。

2未充分利用硬件資源。好比你的機器是16個核心的,可是你的程序是單線程運行的,即便你的程序優化的很好,當須要處理的資源比較多的時候,程序還會很慢,所以如今都在提倡分佈式,經過大量廉價的PC機來提高程序的執行速度!

3其餘服務器反應緩慢,如數據庫、緩存等。當大量作了分佈式,程序CPU負載都很低,可是提交給數據庫的sql沒法很快執行,也會特別慢。

總結一下,當出現性能問題的時候咱們該怎麼作?

1、CPU太高

一、  us太高

使用監控工具快讀定位哪裏有死循環,大計算,對於死循環經過阻塞式隊列解決,對於大計算,建議分配單獨的機器作後臺計算,儘可能不要影響用戶交互,若是必定要的話(如框計算、雲計算),只能經過大量分佈式來實現

二、  sy太高

最有效的方法就是減小進程,不是進程越多效率越高,通常來講線程數和CPU的核心數相同,這樣既不會形成線程切換,又不會浪費CPU資源

2、內存消耗太高

一、  及時釋放沒必要要的對象

二、  使用對象緩存池緩衝

三、  採用合理的緩存失效算法(還記得咱們以前提到的弱引用、幽靈引用麼?)

3、磁盤IO太高

一、  異步讀寫文件

二、  批量讀寫文件

三、  使用緩存技術

四、  採用合理的文件讀寫規則

4、網絡

1、增長寬帶流量

5、資源消耗很少但程序運行緩慢

1、使用併發包,減小鎖競爭

2、對於必須單線程執行的使用隊列處理

3、大量分佈式處理

6、未充分利用硬件資源

一、  修改程序代碼,使用多線程處理

二、  修正外部資源瓶頸,作業務拆分

三、  使用緩存

相關文章
相關標籤/搜索