深入理解JVM效能調優
轉載自:https://blog.csdn.net/elvis_lfc/article/details/52313400
我們知道,效能問題無非就這麼幾種:CPU、記憶體、磁碟IO、網路。那我們來逐一介紹以下相關的現象和一些可能出現的問題。
一、CPU過高。
檢視CPU最簡單的我們使用工作管理員檢視,如下圖所示,windows下使用工作管理員檢視,Linux下使用top檢視。
一般我們的伺服器都採用Linux,因此我們重點關注一下Linux(注:windows模式下相信大家已經很熟悉了,並且前面我們已經提到,使用資源監視器可以很清楚的看到系統的各項引數,在這裡我就不多做介紹了)
在top檢視下,對於多核的CPU,顯示的CPU資源有可能超過100%,因為這裡顯示的是所有CPU佔用百分百的總和,如果你需要看單個CPU的佔用情況,直接按鍵1就可以看到。如下圖所示,我的一臺測試機為8核16GB記憶體。
在
top 檢視下,按鍵 shift+h 後,會顯示各個執行緒的 CPU 資源消耗情況,如下圖所示:我們也可以通過
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次
除了
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;
//……
publicvoid run() {
while(true){
try {
SendMsg sendMsg = queue.poll();//從佇列中取出
if(null != sendMsg) {
sendForQueue(sendMsg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
問題很顯然了,我們看一下對應BlockingQueue的poll方法的API文件。
不難理解了,雖然使用了阻塞的佇列,但是使用了非阻塞的取法,當資料為空時直接返回
null ,那這個語句就等價於下面的語句。@Override
publicvoid run() {
while(true){
}
}
相當於死迴圈麼,很顯然是非常耗費CPU資源的,並且我們還可以發現這樣的死迴圈是耗費的單顆CPU資源,因此可以解釋上圖為啥有一顆CPU佔用特別高。我們來看一下部署在Linux下的top檢視。
猛一看,不是很高麼?我們按鍵
1 來看每個單獨 CPU 的情況!這下看的很清楚了吧!明顯一顆
CPU 被跑滿了。(因為一個單獨的死迴圈只能用到一顆 CPU ,都是單執行緒執行的)。問題找到,馬上修復程式碼為阻塞時存取,如下所示:
@Override
publicvoid 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
*/
publicclass SY_Hign_TestCase {
privatefinalstaticintLOCK_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();
}
abstractclass Task implements Runnable{
protected Object lock;
public Task(int index) {
this.lock= locks[index];
}
@Override
publicvoid run() {
while(true){ //迴圈執行自己要做的事情
doSth();
}
}
//做類自己要做的事情
publicabstractvoid doSth();
}
//任務A 休眠自己的鎖
class TaskA extends Task{
public TaskA(int index) {
super(index);
}
@Override
publicvoid 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
publicvoid doSth() {
try {
synchronized (lock) {
lock.notifyAll();
Thread.sleep(random.nextInt(10));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//啟動函式
publicvoid start(){
ExecutorService service = Executors.newCachedThreadPool();
for(int i=0;i<LOCK_COUNT;++i){
service.execute(new TaskA(i));
service.execute(new TaskB(i));
}
}
//主函式入口
publicstaticvoid main(String[] args) {
new SY_Hign_TestCase().start();
}
}
程式碼很簡單,就是建立了2000個執行緒,讓一定的執行緒去等待,另外一個執行緒去釋放這些資源,這樣就會有大量的執行緒切換,我們來看下效果。
很明顯,
CPU 的核心佔用率很高,我們拿具體的資源監視器看一下:很明顯可以看出有很多執行緒切換佔用了大量的
CPU 資源。同樣的程式部署在Linux下,top檢視如下圖所示:
展開對應的
CPU 資源,我們可以清晰的看到如下情形:大家可以看到有大量的
sy 核心佔用,但是也有不少的 us , us 是因為我們啟用了大量的迴圈,而 sy 是因為大量執行緒切換導致的。我們也可以使用vmstat來檢視,如下圖所示:
二、檔案
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
*/
publicclass IO_TestCase {
private String fileNmae = "monitor.log";
private String context ;
// 和CPU處理器個數相同,既充分利用CPU資源,又導致執行緒頻繁切換
privatefinalstaticintTHRED_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
publicvoid 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();
}
}
}
}
}
//啟動函式
publicvoid start(){
ExecutorService service = Executors.newCachedThreadPool();
for(int i=0;i<THRED_COUNT;++i)
service.execute(new Task());
}
//主函式入口
publicstaticvoid main(String[] args) {
new IO_TestCase().start();
}
}
這段示例很簡單,通過建立一個和CPU個數相同的執行緒池,然後開啟這麼多執行緒一起讀寫同一個檔案,這樣就會因IO資源的競爭而導致IO的佇列很高,如下圖所示:
關掉之後馬上就下來了
我們把這個部署到
Linux 上觀看。這裡的
%idle 指的是系統沒有完成寫入的數量佔用 IO 總量的百分百,為什麼這麼高我們的系統還能承受?因為我這臺機器的記憶體為 16GB 的,我們來檢視以下 top 檢視就可以清晰的看到。佔用了大量的記憶體資源。
三、記憶體消耗
對於JVM的記憶體模型大家已經很清楚了,前面我們講了JVM的效能監控工具。對於Java應用來說,出現問題主要消耗在於JVM的記憶體上,而JVM的記憶體,JDK已經給我們提供了很多的工具。在實際的生成環境,大部分應用會將-Xms和-Xmx設定為相同的,避免執行期間不斷開闢記憶體。
對於記憶體消耗,還有一部分是直接實體記憶體的,不在堆空間,前面我們也寫過對應的示例。之前一個系統就是因為有大量的NIO操作,而NIO是使用實體記憶體的,並且開闢的實體記憶體是在觸發FULL GC的時候才進行回收的,但是當時的機器總記憶體為16GB 給堆的記憶體是14GB Edon為1.5GB,也就是實際剩下給物理呢哦村的只有0.5GB,最終導致總是發生記憶體溢位,但監控堆、棧的記憶體消耗都不大。在這裡我就不多寫了!
四、網路消耗過大
Windows下使用本地網路檢視可以監控當前的網路流量大小
更詳細的資料可以開啟資源監視器,如下圖所示
Linux
平臺可以使用以下 sar 命令檢視sar -n DEV 1 2
欄位說明:
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、 使用快取相關文章
- 【深入理解JVM】8、JVM實戰調優+GC演算法+JVM調優如何定位問題+常見的定位JVM優化命令【面試必備】JVMGC演算法優化面試
- JVM效能調優與實戰篇JVM
- 《java學習三》jvm效能優化-------調優JavaJVM優化
- 深入理解JVM(③)Java的鎖優化JVMJava優化
- 深入理解JVM:效能分析與監控工具JVM
- JVM效能調優,記憶體分析工具JVM記憶體
- JVM | 第1部分:自動記憶體管理與效能調優《深入理解 Java 虛擬機器》JVM記憶體Java虛擬機
- 深入理解 JVMJVM
- 深入理解JVMJVM
- 效能調優(cpu/IO/JVM記憶體分析)JVM記憶體
- JVM調優JVM
- 深入理解JVM——物件JVM物件
- [譯]深入理解JVM Understanding JVM InternalsJVM
- 阿里面試100%問到,JVM效能調優篇阿里面試JVM
- JVM效能調優與實戰進階篇-上JVM
- jvm優化理解JVM優化
- JVM調優策略JVM
- JVM效能優化JVM優化
- JVM效能調優的6大步驟,及關鍵調優引數詳解JVM
- 深入理解JVM虛擬機器6:深入理解JVM類載入機制JVM虛擬機
- JVM調優淺談JVM
- JVM調優推薦JVM
- 掌握JVM調優命令JVM
- 深入理解JVM(一)JVM記憶體模型JVM記憶體模型
- 深入理解JVM(一)——JVM記憶體模型JVM記憶體模型
- 【JAVA進階架構師指南】之五:JVM效能調優Java架構JVM
- JVM效能調優與實戰基礎理論篇-下JVM
- JVM效能優化 (一) 初識JVMJVM優化
- 深入理解JVM(③)虛擬機器效能監控、故障處理工具JVM虛擬機
- [java]深入剖析Java效能監控調優視訊教程Java
- 深入理解 JVM 之 JVM 記憶體結構JVM記憶體
- 深入理解JVM(1)之--JVM記憶體模型JVM記憶體模型
- JVM調優:HotSpot JVM垃圾收集器JVMHotSpot
- Spark 效能調優--資源調優Spark
- 技能篇:linux服務效能問題排查及jvm調優思路LinuxJVM
- JVM效能調優-演算法內功之剖析標記清除JVM演算法
- JVM調優-學習篇JVM
- "簡單"的jvm調優JVM