深入理解JVM性能调优
深入理解JVM性能調優
| 您的評價: | ????? | ?收藏該經驗???? |
width="728" height="90" frameborder="0" marginwidth="0" marginheight="0" vspace="0" hspace="0" allowtransparency="true" scrolling="no" allowfullscreen="true" id="aswift_0" name="aswift_0" style="padding: 0px; margin: 0px; left: 0px; position: absolute; top: 0px;">
??? 在上文中我們分析了很多性能監控工具,介紹這些工具的目的只有一個,那就是找出對應的性能瓶頸。盲目的性能調優是沒有效果的,只有充分知道了哪里出了問題,針對性的結果才是立竿見影的。解決了主要的性能問題,那些次要的性能問題也就不足為慮了!
我們知道,性能問題無非就這么幾種: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?BlockingQueuequeue;
????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來查看,如下圖所示:
?二、文件
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?視圖就可以清晰的看到。
?占用了大量的內存資源。
三、內存消耗
對于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、??使用緩存
轉自:http://yhjhappy234.blog.163.com/blog/static/3163283220122298232721/?suggestedreading&wumii
width="728" height="90" frameborder="0" marginwidth="0" marginheight="0" vspace="0" hspace="0" allowtransparency="true" scrolling="no" allowfullscreen="true" id="aswift_1" name="aswift_1" style="padding: 0px; margin: 0px; left: 0px; position: absolute; top: 0px;">
相關資訊? — 更多
|
? | 相關文檔? — 更多
|
總結
以上是生活随笔為你收集整理的深入理解JVM性能调优的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python如何设计系统界面教程_pyt
- 下一篇: 搞笑的昵称812个