在上文中咱們分析了不少性能監控工具,介紹這些工具的目的只有一個,那就是找出對應的性能瓶頸。盲目的性能調優是沒有效果的,只有充分知道了哪裏出了問題,針對性的結果纔是立竿見影的。解決了主要的性能問題,那些次要的性能問題也就不足爲慮了!html
咱們知道,性能問題無非就這麼幾種:CPU、內存、磁盤IO、網絡。那咱們來逐一介紹如下相關的現象和一些可能出現的問題。java
1、CPU太高。ios
查看CPU最簡單的咱們使用任務管理器查看,以下圖所示,windows下使用任務管理器查看,Linux下使用top查看。web
通常咱們的服務器都採用Linux,所以咱們重點關注一下Linux(注:windows模式下相信你們已經很熟悉了,而且前面咱們已經提到,使用資源監視器能夠很清楚的看到系統的各項參數,在這裏我就很少作介紹了)算法
在top視圖下,對於多核的CPU,顯示的CPU資源有可能超過100%,由於這裏顯示的是全部CPU佔用百分百的總和,若是你須要看單個CPU的佔用狀況,直接按鍵1就能夠看到。以下圖所示,個人一臺測試機爲8核16GB內存。sql
在數據庫
top 視圖下,按鍵 shift+h 後,會顯示各個線程的 CPU 資源消耗狀況,以下圖所示:windows
咱們也能夠經過緩存
sysstat 工具集的 pidstat 來查看服務器
注:sysstat下載地址:http://sebastien.godard.pagesperso-orange.fr/download.html
安裝方法:
一、chmod +x configure
二、./configure
三、make
四、make install
如輸入pidstat 1 2就會隔一秒在控制檯輸出一次固然CPU的狀況,共輸出2次
除了
top 、 pidstat 之外, vmstat 也能夠進行採樣分析
相關
top 、 pidstat 、 mstat 的用法你們能夠去網上查找。
下面咱們主要來介紹如下當出現CPU太高的時候,或者CPU不正常的時候,咱們該如何去處理?
CPU消耗太高主要分爲用戶進程佔用CPU太高和內核進程佔用CPU太高(在Linux下top視圖下us指的是用戶進程,而sy是指內核進程),咱們來看一個案例:
程序運行前,系統運行平穩,其中藍色的線表示總的
CPU 利用率,而紅色的線條表示內核使用率。部署 war 測試程序,運行以下圖所示:
對於一個
web 程序,尚未任何請求就佔用這麼多 CPU 資源,顯然是不正常的。而且咱們看到,不是系統內核佔用的大量 CPU ,而是系統進程,那是哪個進程的呢?咱們來看一下。
很明顯是咱們的
java 進程,那是那個地方致使的呢?這就須要用到咱們以前提到的性能監控工具。在此咱們使用可視化監控工具 VisualVM。
首先咱們排除了是
GC 過於頻繁而致使大 CPU 太高,由於很明顯監控視圖上沒有 GC 的活動。而後咱們打開 profilter 去查看如下,是那個線程致使了 CPU 的太高?
前面一些線程都是容器使用的,而下面一個線程也一直在執行,那是什麼地方調用的呢?查找代碼中使用
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文檔。
不難理解了,雖然使用了阻塞的隊列,可是使用了非阻塞的取法,當數據爲空時直接返回
null ,那這個語句就等價於下面的語句。
@Override
public void run() {
while(true){
}
}
至關於死循環麼,很顯然是很是耗費CPU資源的,而且咱們還能夠發現這樣的死循環是耗費的單顆CPU資源,所以能夠解釋上圖爲啥有一顆CPU佔用特別高。咱們來看一下部署在Linux下的top視圖。
猛一看,不是很高麼?咱們按鍵
1 來看每一個單獨 CPU 的狀況!
這下看的很清楚了吧!明顯一顆
CPU 被跑滿了。(由於一個單獨的死循環只能用到一顆 CPU ,都是單線程運行的)。
問題找到,立刻修復代碼爲阻塞時存取,以下所示:
@Override
public void run() {
while(true){
try {
SendMsg sendMsg = queue.take();//從隊列中取出
sendForQueue(sendMsg);
} catch (Exception e) {
e.printStackTrace();
}
}
}
再來監控
CPU 的變換,咱們能夠看到,基本上不消耗 CPU 資源(是我沒作任何的訪問哦,有用戶創建線程就會消耗)。
再來看
java 進程的消耗,基本上不消耗 CPU 資源
再來看VisualVM的監控,咱們就能夠看到基本上都是容器的一些線程了
以上示例展現了
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個線程,讓必定的線程去等待,另一個線程去釋放這些資源,這樣就會有大量的線程切換,咱們來看下效果。
很明顯,
CPU 的內核佔用率很高,咱們拿具體的資源監視器看一下:
很明顯能夠看出有不少線程切換佔用了大量的
CPU 資源。
一樣的程序部署在Linux下,top視圖以下圖所示:
展開對應的
CPU 資源,咱們能夠清晰的看到以下情形:
你們能夠看到有大量的
sy 內核佔用,可是也有很多的 us , us 是由於咱們啓用了大量的循環,而 sy 是由於大量線程切換致使的。
咱們也可使用vmstat來查看,以下圖所示:
2、文件
IO 消耗過大,磁盤隊列高。
在windows環境下,咱們可使用資源監視器查看對應的IO消耗,以下圖所示:
這裏不但能夠看到當前磁盤的負載信息,隊列詳情,還能看到每一個單獨的進程的資源消耗狀況。
Linux下主要使用pidstat、iostat等進行分析。以下圖所示
Pidstat –d –t –p [pid] {time} {count}
如:pidstat -d -t -p 18720 1 1
Iostat
Iostat –x xvda 1 10作定時採樣
廢話很少說,直接來示例,上乾貨!
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的隊列很高,以下圖所示:
關掉以後立刻就下來了
咱們把這個部署到
Linux 上觀看。
這裏的
%idle 指的是系統沒有完成寫入的數量佔用 IO 總量的百分百,爲何這麼高咱們的系統還能承受?由於我這臺機器的內存爲16GB 的,咱們來查看如下 top 視圖就能夠清晰的看到。
佔用了大量的內存資源。
3、內存消耗
對於JVM的內存模型你們已經很清楚了,前面咱們講了JVM的性能監控工具。對於Java應用來講,出現問題主要消耗在於JVM的內存上,而JVM的內存,JDK已經給咱們提供了不少的工具。在實際的生成環境,大部分應用會將-Xms和-Xmx設置爲相同的,避免運行期間不斷開闢內存。
對於內存消耗,還有一部分是直接物理內存的,不在堆空間,前面咱們也寫過對應的示例。以前一個系統就是由於有大量的NIO操做,而NIO是使用物理內存的,而且開闢的物理內存是在觸發FULL GC的時候才進行回收的,可是當時的機器總內存爲16GB 給堆的內存是14GB Edon爲1.5GB,也就是實際剩下給物理呢哦村的只有0.5GB,最終致使老是發生內存溢出,但監控堆、棧的內存消耗都不大。在這裏我就很少寫了!
4、網絡消耗過大
Windows下使用本地網絡視圖能夠監控當前的網絡流量大小
更詳細的資料能夠打開資源監視器,以下圖所示
Linux
平臺可使用如下 sar 命令查看
sar -n DEV 1 2
字段說明:
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、未充分利用硬件資源
一、 修改程序代碼,使用多線程處理
二、 修正外部資源瓶頸,作業務拆分
三、 使用緩存