1. 程式人生 > >Jmeter效能測試-GC相關

Jmeter效能測試-GC相關

https://www.cnblogs.com/danqiu/p/6009016.html

 

Jmeter效能測試-GC相關

1.GC相關

HotSpot虛擬機器將其物理上劃分為兩個–新生代(young generation)和老年代(old generation)。
新生代(Young generation): 絕大多數最新被建立的物件會被分配到這裡,由於大部分物件在建立後會很快變得不可到達,所以很多物件被建立在新生代,然後消失。物件從這個區域消失的過程我們稱之為”minor GC“。

老年代(Old generation): 物件沒有變得不可達,並且從新生代中存活下來,會被拷貝到這裡。其所佔用的空間要比新生代多。也正由於其相對較大的空間,發生在老年代上的GC要比新生代少得多。物件從老年代中消失的過程,我們稱之為”major GC

“(或者”full GC“)

圖中的持久代( permanent generation )也被稱為方法區method area)。他用來儲存類常量以及字串常量。因此,這個區域不是用來永久的儲存那些從老年代存活下來的物件。這個區域也可能發生GC。並且發生在這個區域上的GC事件也會被算為major GC。

新生代是用來儲存那些第一次被建立的物件,他可以被分為三個空間

  •  一個伊甸園空間(Eden 
  •  兩個倖存者空間(Survivor )
  • 絕大多數剛剛被建立的物件會存放在伊甸園空間。
  • 在伊甸園空間執行了第一次GC之後,存活的物件被移動到其中一個倖存者空間。
  •   此後,在伊甸園空間執行GC之後,存活的物件會被堆積在同一個倖存者空間。
  •  當一個倖存者空間飽和,還在存活的物件會被移動到另一個倖存者空間。之後會清空已經飽和的那個倖存者空間。
  • 在以上的步驟中重複幾次依然存活的物件,就會被移動到老年代。

jstat 是HotSpot JVM提供的一個監控工具

jstat –gc  $<pid$> 1000

 

S0C       S1C       S0U    S1U      EC         EU          OC         OU         PC         PU         YGC     YGCT    FGC      FGCT     GCT

3008.0   3072.0    0.0     1511.1   343360.0   46383.0     699072.0   283690.2   75392.0    41064.3    2540    18.454    4      1.133    19.588

3008.0   3072.0    0.0     1511.1   343360.0   47530.9     699072.0   283690.2   75392.0    41064.3    2540    18.454    4      1.133    19.588

3008.0   3072.0    0.0     1511.1   343360.0   47793.0     699072.0   283690.2   75392.0    41064.3    2540    18.454    4      1.133    19.588

 

這些資訊很重要,因為它們展示了GC處理到底花費了多少時間。

在這個例子中,YGC 是217而YGCT 是0.928,這樣在簡單的計算資料平均數後,你可以知道每次新生代的GC大概需要4ms(0.004秒),而full GC的平均時間為33ms。

但是,只看資料平均數經常無法分析出真正的GC問題。這是主要是因為GC操作時間嚴重的偏差(換句話說,假如兩次full GC的時間是 67ms,那麼其中的一次full GC可能執行了10ms而另一個可能執行了57ms。)為了更好地檢測每次GC處理時間,最好使用 –verbosegc來替代資料平均數。

為什麼需要優化GC

或者說的更確切一些,對於基於Java的服務,是否有必要優化GC應該說,對於所有的基於Java的服務,並不總是需要進行GC優化,但前提是所執行的基於Java的系統,包含了如下引數或行為:

  • 已經通過 -Xms 和–Xmx 設定了記憶體大小
  • 包含了 -server 引數
  • 系統中沒有超時日誌等錯誤日誌

換句話說,如果你沒有設定記憶體的大小,並且系統充斥著大量的超時日誌時,你就需要在你的系統中進行GC優化了。

但是,你需要時刻銘記一條GC優化永遠是最後一項任務。

我為GC優化歸納了兩個目的:

  1. 一個是將轉移到老年代的物件數量降到最少
  2. 另一個是減少Full GC的執行時間

 

將轉移到老年代的物件數量降到最少

按代的GC機制由Oracle JVM提供,不包括可以在JDK7以及更高版本中使用的G1 GC。換句話說,物件被建立在伊甸園空間,而後轉化到倖存者空間,最終剩餘的物件被送到老年代。某些比較大的物件會在被建立在伊甸園空間後,直接轉移到老 年代空間。老年代空間上的GC處理會新生代花費更多的時間。因此,減少被移到老年代物件的資料可以顯著地減少Full GC的頻率。減少被移到老年代空間的物件的數量,可能被誤解為將物件留在新生代。但是,這是不可能的。取而代之,你可以調整新生代空間的大小。

減少Full GC執行時間

Full GC的執行時間比Minor GC要長很多。因此,如果Full GC花費了太多的時間(超過1秒),一些連線的部分可能會發生超時錯誤。

  • 如果你試圖通過消減老年代空間來減少Full GC的執行時間,可能會導致OutOfMemoryError 或者 Full GC執行的次數會增加。
  • 與之相反,如果你試圖通過增加老年代空間來減少Full GC執行次數,執行時間會增加。

因此,你需要將老年代空間設定為一個“合適”的值。

影響GC效能的引數

正如我們在第二篇文章結尾提到的,不要幻想“某個人設定了GC引數後效能得到極大的提高,我們為什麼不和他用一樣的引數?”,因為不同的Web服務所建立物件的大小和他們的生命週期都不盡相同。

簡單來說,如果一個任務的執行條件是A,B,C,D和E,同樣的任務執行條件換為A和B,你會覺得哪個更快?從一般人的直覺來看,在A和B條件下執行的任務會更快。

Java GC引數也是相同的道理,設定一些引數不但沒有提高GC執行速度,反而可能導致他更慢。GC優化的最基本原則是將不同的GC引數用於2臺或者多臺伺服器,並進行對比,並將那些被證明提高了效能或者減少了GC執行時間的引數應用於伺服器。請謹記這一點。

下面這個表格列出了GC引數中與記憶體大小相關的,可以影響效能的引數。

1GC優化需要考慮的Java引數

定義

引數

描述

堆記憶體空間

-Xms

Heap area size when starting JVM

啟動JVM時的堆記憶體空間。

 

-Xmx

Maximum heap area size

堆記憶體最大限制

新生代空間

-XX:NewRatio

Ratio of New area and Old area

新生代和老年代的佔比

 

-XX:NewSize

New area size

新生代空間

 

-XX:SurvivorRatio

Ratio ofEdenarea and Survivor area

伊甸園空間和倖存者空間的佔比

我在進行GC優化時經常使用-Xms,-Xmx和-XX:NewRatio。-Xms和-Xmx是必須的。你如何設定NewRatio 會對GC效能產生十分顯著的影響。有些人可能會問如何設定Perm區域的大小?你可以通過-XX:PermSize 和-XX:MaxPermSize引數來設定,

當OutOfMemoryError 錯誤發生並且是由於Perm空間不足導致時,另一個可能影響GC效能的引數是GC型別。下表列出了所有可選的GC型別(基於JDK6.0)

在分析監控結果後,決定是否進行GC優化

在檢查GC狀態的過程中,你應該分析監控結果以便決定是否進行GC優化,如果分析結果表明執行GC的時間只有0.1-0.3秒,那你就沒必要浪費時間去進行GC優化。但是,如果GC的執行時間是1-3秒,或者超過10秒,GC將勢在必行。

但是,如果你已經為Java分配了10GB的記憶體,並且不能再減少記憶體大小,你將無法再對GC進行優化。在進行GC優化 之前,你必須想清楚你為什麼要分配如此大的記憶體空間。假如當你分1 GB 或 2 GB記憶體時出現OutOfMemoryError ,你應該執行堆記憶體轉儲(heap dump),並消除隱患。

注意:

堆記憶體轉儲是一個用來檢查Java記憶體中的物件和資料的檔案。該檔案可以通過執行JDK中的jmap命令來建立。在建立檔案的過程中,Java程式會暫停,因此不要再系統執行過程中建立該檔案。

如果GC執行時間滿足下面所有的條件,就意味著無需進行GC優化了。

  • Minor GC執行的很快(小於50ms)
  • Minor GC執行的並不頻繁(大概10秒一次)
  • Full GC執行的很快(小於1s)
  • Full GC執行的並不頻繁(10分鐘一次)

上面提到的數字並不是絕對的;他們根據服務狀態的不同而有所區別,某些服務可能滿足於Full GC每次0.9秒的速度,但另一些可能不是。因此,針對不同的服務設定不同的值以決定是否進行GC優化。

  • 設定記憶體空間大小

下表展示了記憶體空間大小,GC執行次數以及GC執行時間三者間的關係。

    • 大記憶體空間
      • 減小GC執行次數
      • 增加GC執行時間
    • 小記憶體空間
      • 減小GC執行時間
      • 增加GC執行次數

關於如何設定記憶體空間的大小,沒有唯一的標準答案。如果服務 器資源足夠,而且Full GC也可能在1秒內完成,設定為10GB當然可行。。但絕大多數伺服器並不是這樣,當記憶體設為10GB時,可能要花費10~30秒來執行Full GC。當然,執行時間會隨物件的大小而改變。

鑑於如此,我們應該如何設定記憶體空間大小呢?一 般來說,我建議為500MB。不過請注意這不是讓你將WAS的記憶體引數設定為–Xms500m 和–Xmx500m。根據優化GC之前的狀態,如果 Full GC執行之後記憶體空間剩餘300MB,那麼最好將記憶體設定為1GB(300MB(預設程式佔用)+ 500MB(老年代最小空間)+200MB(空閒記憶體))。也就是說你要為老年代額外設定500MB。因此,如果你有三個執行伺服器,記憶體分別設定為 1GB,1.5GB,2GB,並且檢查結果。

理論上來講,GC執行速度應該遵循1GB> 1.5GB> 2GB,因此1GB執行GC速度最快。但是並不說明1GB空間的Full GC會花費1秒而2GB空間會花費2秒。時間取決於伺服器的效能和物件的大小。因此,最佳的方式是建立儘可能多的衡量指標來監控他們。

對於記憶體空間大小,你應該額外設定NewRatio引數。 NewRatio引數是新生代和老年代空間的比例,即XX:NewRatio=1意味著新生代與老年代之比為1:1。對於1GB來說就是新生代和老年代各 500MB。如果NewRatio為2,意味著新生代老年代之比為1:2,因此該值越大,老年代空間越大,新生代空間越小。

這看似一件不是很重要的事情,但NewRatio引數會顯著地影響整個GC的效能。如果新生代空間很小,會用更多的物件被轉移到老年代空間,這樣導致頻繁的Full GC,增加暫停時間。

你可以簡單的認為NewRatio 為1是最佳的選擇,但是,有時可能設定為2或3更好,我就見過很多這樣的例子。

如何最快的完成GC優化?對比效能測試的結果 應該是最快地方法,為每一臺伺服器設定不同的引數並監控他們的狀態,強烈建議至少監控1或2天的資料。但是,當你對GC優化是,你要確保每次執行相同的負 載。並且請求的比率,例如URL都應該是一致的。不過,即便對於專業測試人員要想精確的控制負載也是很難的,並要花費大量的時間準備。因此,相對來說比較 方便和容易的方法是調整才引數,之後花費較長的時間收集結果。 

示例1

下面這個例子針對 Service S的優化,對於最近被部署的 Service S,Full GC花費了太長的時間。

請看 jstat –gcutil的執行結果。

 

1

2

S0 S1 E O P YGC YGCT FGC FGCT GCT

12.16 0.00 5.18 63.78 20.32 54 2.047 5 6.946 8.993

 

最左邊的Perm 空間對於最初的GC優化不是很重要,這一次YGC引數的值更加有用。

Minor GC和Full GC的平均值如下表所示

3Service SMinor GC Full GC的平均執行時間

GC 型別

GC 執行次數

GC 執行時間

平均

Minor GC

54

2.047

37 ms

Full GC

5

6.946

1,389 s

最重要的是下面兩個資料

  • 新生代實際使用空間: 212,992 KB
  • 老年代實際使用空間: 1,884,160 KB

因此,總的記憶體空間為2GB,不算Perm空間的話,新生代與老年代之比為1:9。通過jstat和-verbosegc 日誌進行資料收集,並把三臺伺服器按照如下方式設定。

  • NewRatio=2
  • NewRatio=3
  • NewRatio=4

一天之後,檢查系統的GC日誌後發現,在設定了NewRatio引數後很幸運的沒有發生Full GC,

為什麼?

  • NewRatio=2: 45 ms
  • NewRatio=3: 34 ms
  • NewRatio=4: 30 ms

我們看到NewRatio=4 是最佳的引數,雖然它的新生代空間最小,但GC時間確最短。設定這個引數之後,系統沒有執行過Full GC。

為了說明這個問題,下面是服務之星一段時間後執行jstat –gcutil的結果

 

1

2

S0 S1 E O P YGC YGCT FGC FGCT GCT

8.61 0.00 30.67 24.62 22.38 2424 30.219 0 0.000 30.219

 

你可能會認為因為伺服器接受的請求少才導致的GC執行頻率下降。實際上,雖然Full GC沒有執行,但是Minor GC被執行了 2,424次。

示例2

這是一個針對ServiceA的例子,我們通過公司內部的應用效能管理系統(APM)發現JVM暫停了相當長的時間(超過8秒),因此我們進行了GC優化。我們找到了Full GC執行時間過長的原因,並著手解決。

進行GC優化的第一步,就是我們添加了-verbosegc引數,並得到如下結果。

1:進行GC優化之前的STW時間

如上圖所示,由HPJMeter自動生成的圖片之一。X座標表示JVM執行的時間。Y座標表示每次GC的時間。CMS綠點,表示Full GC結果。Parallel Scavenge藍點,表示Minor GC結果。

之前我曾經說過CMS GC是最快的,但是上面的的結果顯示出於某種原因,它最多花費了15秒。是什麼導致這個結果?是否想起我之前提過的,CMS在進行記憶體清理時,會變慢。與此同時,服務的記憶體被設定為 –Xms1g和–Xmx4g ,且實際分配了4GB記憶體。

因此,我將GC型別從CMS改為Parallel GC。並且將記憶體改為2GB,設定NewRatio 為3。幾小時之後我使用 jstat –gcutil得到如下結果

 

1

2

S0 S1 E O P YGC YGCT FGC FGCT GCT

0.00 30.48 3.31 26.54 37.01 226 11.131 4 11.758 22.890

 

相對於4GB時的15秒,Full GC變成了平均每次3秒。但是3秒一樣比較慢,因此我設計瞭如下6種場景。

  • Case 1: -XX:+UseParallelGC -Xms1536m -Xmx1536m -XX:NewRatio=2
  • Case 2: -XX:+UseParallelGC -Xms1536m -Xmx1536m -XX:NewRatio=3
  • Case 3: -XX:+UseParallelGC -Xms1g -Xmx1g -XX:NewRatio=3
  • Case 4: -XX:+UseParallelOldGC -Xms1536m -Xmx1536m -XX:NewRatio=2
  • Case 5: -XX:+UseParallelOldGC -Xms1536m -Xmx1536m -XX:NewRatio=3
  • Case 6: -XX:+UseParallelOldGC -Xms1g -Xmx1g -XX:NewRatio=3

那一個最快呢?結果顯示,記憶體越小,結果越好。下圖展示了Case6的結果。這是GC的效能最好。最長的響應時間只有1.7秒。平均時間在1秒之內。

2Case6的時間圖表

基於以上結果。我們按照Case6調整了GC引數。但是,這導致了每天晚上都會發生OutOfMemoryError。在這裡很難解釋具體的原因。簡單來說,批處理程式導致了記憶體洩漏。相關的問題已經被解決。

如果對GC日誌只分析很短的時間就貿然對所有伺服器進行優化是非常危險的。請時刻牢記,你必須同時分析GC日誌和應用程式。

我們回顧了兩個關於GC優化的例子,正如我之前提到的,例子中提到的GC引數,可以設定在相同的伺服器之上,但前提是他們具有相同的CPU,作業系統,JDK版本以及執行著相同的服務。但是不要直接把我用過的引數用到你的服務至上,它們未必能很好的工作。

 

切實地調優

如果測試的結果滿足了預期,那麼你不需要對程式進行效能調優。如果沒有達到預期結果,你就應該執行調優來解決問題。接下來會通過例項講解方法。

stop-the-world耗時過長

stop-the-world耗時過長可能是由於GC引數不合理或者程式碼實現不正確。你可以通過分析工具或堆記憶體轉儲檔案(Heap dump)來定位問題,比如檢查堆記憶體中物件的型別和數量。如果在其中找到了很多不必要的物件,那麼最好去改進程式碼。如果沒有發現建立物件的過程中有特別 的問題,那麼最好單純地修改GC引數。

為了適當地調整GC引數,你需要獲取一段足夠長時間的GC日誌,還必須知道哪些情況會導致長時間的stop-the-world。想了解更多關於如何選擇合適的GC引數,可以閱讀我同事的一篇博文:How to Monitor Java Garbage Collection

CPU使用率過低

當系統發生阻塞,吞吐量和CPU使用率都會降低。這可能是由於網路系統或者併發的問題。為了解決這個問題,你可以分析執行緒轉儲資訊(Thread dump)或者使用分析工具。閱讀這篇文章可以獲得更多關於執行緒轉儲分析的知識:How to Analyze Java Thread Dumps

你可以使用商業的分析工具對執行緒鎖進行精確的分析,不過大部分時候,只需使用JVisualVM中的CPU分析器,就能獲得足夠的資訊。

CPU使用率過高

如果吞吐量很低但是CPU使用率卻很高,很可能是低效率程式碼導致的。這種情況下,你應該使用分析工具定位程式碼中效能的瓶頸。可使用的工具有:JVisualVMEclipse TPTP或者JProbe

調優方法

建議你使用如下方法對程式進行調優。

首先,檢查效能調優是否必要。測量效能不是一件簡單的工作,你也不能保證每次都獲得滿意的結果。因此如果程式已經滿足預期效能需求,不必在調優上增加額外的投入了。

問題只出在一個地方,你要做的就是去解決掉它。二八定律(Pareto principle)對效能調優同樣適用。這不是說某個模組的低效能一定只源於一個問題,而是強調我們應該在調優時把注意力放在影響最大的那個問題上。在處理好了最重要的之後,你才應該去解決剩下其他的。也就是建議一次只對一個問題進行修復。

另外需要考慮到氣球效應(Balloon effect),有得必有失。你可以通過使用快取來提高響應能力,但是當快取逐漸增大,執行一次Full GC的時間也會更長。一般而言,如果你希望記憶體使用率比較低,那麼吞吐量和響應能力可能都會惡化。因此,要知道什麼對自己程式來說最重要的,而哪些又是次要的。

到此為止,你應該已經瞭解瞭如何對Java程式進行效能調優。為了介紹效能測定的具體過程,我不得不省略其中一些細節,不過我認為這些也足夠應對大多數Java Web服務端程式了

 

2.操作相關

1.如何考慮一個性能需求

1. 通常拿到一個性能需求,需要了解介面模型,整個介面的使用比例和容量,系統結構和技術方案,資料庫快取和索引分佈,設計出合理的測試計劃,設計的原則是儘可能真實的模擬線上情況。

2.根據設計出的測試計劃,使用一個執行緒執行測試計劃,得出測試結果,對比測試指標

3.以線性增長的方式增加併發數,比較測試結果是否為線性增長

4.測試結果平緩區通常代表著系統容量的最大區域,分析此時的測試結果,如 TPS和響應時間等

5.分析資料庫TPS、QPS等資料,redis命中率,MQ吞吐率,javaGC,程式佔用時間等各方面因素提高系統性能

6.通過jstack分析各模組的資源佔用情況。

7.優化後的程式要經過大批量資料測試確保測試程式不會發生錯誤

8.根據測試概要報告,資料庫監控資料等資料整合測試報告

9. 需要關注api層用的是長連線還是短連線,長連線的話需要在jmeter裡勾選keep alive

2.如何保證jmeter自身效能

1.使用命令列啟動,減少介面造成的效能問題

2.命令列記錄聚合報告,不要啟動過多的其他監控報告

3.儘可能關閉不必要的日誌,包括測試程式碼的log,jmeter自身日誌。

4.確保測試指令碼計時準確,需確認好那些步驟需要計時,那些在準備在計時之外

5.過多的併發使用分散式請求,當本地cpu,mem佔用比較大時,修改jmeter.properties,增加遠端remote-server,並啟動(具體操作見jmeter操作手冊)

3.如何分析測試結果

1.根據聚合報告分析90%,95%,和最大等指標,找出程式普遍的響應時間。

2.讓開發人員查詢最大響應時間時的日誌,分析程式層面的執行任務情況

3.使用jstack等分析當前程式的資源等待情況,系統資源情況

值得關注的執行緒狀態有:
     死鎖,Deadlock(重點關注)
     執行中,Runnable  
     等待資源,Waiting on condition(重點關注)
     等待獲取監視器,Waiting on monitor entry(重點關注)
     暫停,Suspended
     物件等待中,Object.wait() 或 TIMED_WAITING
     阻塞,Blocked(重點關注) 
     停止,Parked

示例圖

4.分析gc結果,cpu,IO等情況,分析程式是否充分利用了系統資源

5.對比分析各併發數的程式響應情況。

分類: 效能測試