一步步優化JVM六:優化吞吐量

若是你已經進行完了前面的步驟了,那麼你應該知道這是最後一步了。在這一步裏面,你須要測試應用的吞吐量和爲了更高的吞吐量而優化JVM。html


   這一步的輸入就是應用的吞吐量性能要求。應用的吞吐量是在應用層面衡量而不是在JVM層面衡量,所以,應用必需要報告出一些吞吐量指標或者應用的某些操做的吞吐量性能指標。觀察到的吞吐量指標而後用能夠用來和應用須要的性能指標進行比較,若是達到或者超過要求,那麼這一步就完成了。若是你須要更好的吞吐量的話,有一些JVM優化能夠去作。
 
   這一步的另一個輸入就是,有多少內存能夠供應用使用,就想前面說的GC最大化內存原則,越多可用的內存,性能就更好。這條原則不只僅適用於吞吐量優化,一樣適用於延遲優化。
 
   應用的吞吐量需求多是沒法知足的。若是是這種狀況,那麼就須要從新審視應用吞吐量的需求,應用就須要修改或者改變部署模型。若是上面的一種或者多種狀況發生了,那麼你須要從新進行前面的優化步驟。
 
   在前面的步驟裏面,你可能使用吞吐量垃圾回收器解決了問題(經過-XX:+UseParallelOldGC或者-XX:+UsePrallelGC),或者你調整到併發垃圾回收器(CMS)來解決的問題。若是使用的CMS來解決的問題,下面有一些選項來提高應用的吞吐量,下面詳細介紹。若是是使用的吞吐量垃圾回收器,咱們將在CMS以後介紹。
   
   CMS吞吐量優化
 
   可以用來提高CMS吞吐量的選項數量有限,下面列出一些能夠單獨使用或者聯合使用的選項:
 
   一、使用一些額外的命令選項,在後面的「額外的性能命令行選項」中詳細介紹。
   二、增長young代的空間大小,增長young代的空間大小,能夠減小MinorGC的頻率,就可以減小在一段時間裏面MinorGC佔用的時間。
   三、增長old代的空間大小,增長old代的空間,能夠減小CMS垃圾回收的頻率,減小潛在的碎片,能夠減小
stop-the-world垃圾回收。
   四、進一步優化young代堆大小,已經在前面的「優化延遲和響應時間」裏面說過了,以及如何優化eden空間任務後和survivor空間大小以減小對象從young代移動到old也在前面已經說過了。須要注意的是,當優化eden和survivor空間大小的時候考慮到一些權衡。
   五、優化CMS週期的啓動,也在前面說過了。
   
   任何上面提到的優化,或者組合使用上面的選擇,都是減小垃圾回收器佔用CPU時間,把CPU留給應用計算。前面兩種選擇,提供一種可能性來提高吞吐量,可是會有stop-the-world垃圾回收的風險,會增長延遲。
 
   做爲指導,不考慮CMS,MinorGC的次數應該減小10%,你可能只能下降1%-3%。一般來說,若是隻能減小3%甚至更少,那麼可以提高的吞吐量空間恐怕就有限了。
 
   吞吐量垃圾回收器優化
 
   優化吞吐量垃圾回收器的目標是避免FullGC或者理想狀況下,避免在穩定狀態下FullGC。這個須要優化對象的歲數,這個能夠經過制定survivor空間優化完成。你可讓eden空間更大,能夠減小MinorGC的次數。我知道當對象的任期或者歲數達到必定值的時候就會移動到old代,而這個任期就是對象經歷MinorGC的次數,MinorGC的次數越少,對象任期增加越慢,就有可能被MinorGC回收掉,而不是進入old代。
 
   使用HotSpot VM的吞吐量垃圾回收器,能夠經過-XX:+UseParallelOldGC和-XX:+UsePrallelGC,這樣能夠提供最好的吞吐量。吞吐量垃圾回收器利用了一種叫作自適應大小的特性,自適應大小是基於對象的分配和存活率來自動改變eden空間和survivor空間大小,目的是優化對象的歲數分佈。自適應大小的企圖是提供易用性,容易優化JVM,以至於提供可靠的吞吐量。自適應大小在大多數應用下,可以很好的工做,可是關閉自適應大小以及優化eden空間和survivor空間以及old代空間是一個探索提高應用吞吐量的一種辦法。關閉自適應大小會改變應用的程序的靈活性,尤爲是在修改應用程序,以及隨着時間的推移應用的數據發生了變化。
 
   關閉自適應大小可使用選項:
 
 
   -XX:-UseAdaptiveSizePolicy
 
   注意在「-XX」後面的「-」代表關閉UseAdapivieSizePolicy提供的特性。只有吞吐量垃圾回收器支持這個選項。在其餘的垃圾回收器上使用這個選項是無用的。
 
   另一個可選的命令行選項,能夠產生關於survivor空間佔用更詳細的信息,關於survivor空間是否溢出,對象是否從young代移動到old代,選項是-XX:+PrintAdaptiveSizePolicy。這個選項最好和-XX:+PrintGCDetails以及-XX:+PrintGCDateStamps或者-XX:+PrintGCTimeStamps一塊兒使用。下面是一個垃圾回收的例子-XX:+PrintGCDateStamps, -XX:PrintGCDetails, -XX:-UseAdaptiveSizePolicy (關閉自適應大小), 以及-XX:+PrintAdaptiveSizePolicy:
 
   2010-12-16T21:44:11.444-0600:
[GCAdaptiveSizePolicy::compute_survivor_space_size_and_thresh:
survived: 224408984
promoted: 10904856
overflow: false
[PSYoungGen: 6515579K->219149K(9437184K)]
8946490K->2660709K(13631488K), 0.0725945 secs]
[Times: user=0.56 sys=0.00, real=0.07 secs]
 
   和之前不一樣的是,以GCAdaptiveSizePolicy開頭的一些額外信息輸出來了,survived標籤代表「to」 survivor空間的對象字節數。在這個例子中,survivor空間佔用量是224408984字節,可是移動到old代的字節數卻有10904856字節。overflow代表young代是否有對象溢出到old代,換句話說,就是代表了「to」 survivor是否有足夠的空間來容納從eden空間和「from」survivor空間移動而來的對象。爲了更好的吞吐量,指望在應用處於穩定運行狀態下,survivor空間不要溢出。
 
   爲了開始優化,你應該關閉自適應大小以及獲取在垃圾回收器日誌裏面額外的survivor空間統計信息,使用這兩個選項-XX:-UseAdaptiveSizePolicy以及-XX:+PrintAdaptiveSizePolicy。這樣提供了一些初始化的信息,以幫助作出優化決定。假如以前使用下面的命令行選項:
 
 
 -Xmx13g -Xms13g -Xmn4g -XX:SurvivorRatio=6 -XX:+UseParallelOldGC -XX:PrintGCDateStamps 
-XX:+PrintGCDetails

 
 
  那麼,就應該以下一組命令行選項,來關閉自適應大小和捕獲survivor空間統計信息:
 
 
 -Xmx13g -Xms13g -Xmn4g -XX:SurvivorRatio=6
-XX:+UseParallelOldGC -XX:PrintGCDateStamps -XX:+PrintGCDetails
-XX:-UseAdaptiveSizePolicy -XX:+PrintAdaptiveSizePolicy

 
 
   首先在應用穩定運行狀態下尋找FullGC信息,包括日期和時間戳能夠用來識別出應用是否從啓動狀態進入了穩定狀態。舉例,若是你知道應用啓動須要30秒時間,那麼在應用啓動30秒以後才觀察垃圾回收。
 
   觀察FullGC信息,你可能會發現有一些短存活時間的對象移動到了old代空間,若是FullGC發生了,首先要肯定是old代的空間是FullGC以後存活對象的1.5倍。若是有須要,增長old代的空間來保持1.5倍的指標,這樣,能夠保證old代有足夠的空間來處理不在預期內的轉移率(致使短的存活時間的對象移動到old代)或者一些未知的狀況——致使了對象的轉移過快,擁有這樣的額外空間,能夠延遲甚至可能可以阻止FullGC的發生。
 
   在肯定了old代有足夠的空間以後,就須要觀察MinorGC的情況。首先須要觀察survivor空間是否溢出,若是survivor空間溢出了,那麼overflow標籤會是true,不然,overload字段會是false。下面是一個survivor空間溢出的例子:
 
   2010-12-18T10:12:33.322-0600:
[GCAdaptiveSizePolicy::compute_survivor_space_size_and_thresh:
survived: 446113911
promoted: 10904856
overflow: true
[PSYoungGen: 6493788K->233888K(9437184K)]
7959281K->2662511K(13631488K), 0.0797732 secs]
[Times: user=0.59 sys=0.00, real=0.08 secs]
 
   若是survivor空間溢出,對象會再達到任期閥值或者消亡以前被移動到old代。換句話說,對象過快的移動到old代。頻繁的survivor空間溢出會致使FullGC,下面說如何優化survivor。
   
優化survivor空間
 
   優化survivor空間的目標是保持或者老化短期存活動的對象在young代中,一直到不得不移動到old代中。開始查看每個MinorGC,尤爲是存活的對象字節數。須要注意一點的是,爲了不應用啓動的時候對象對後面分析的干擾,能夠考慮放棄應用剛進入穩定狀態的前面5到10個MinorGC信息。
 
   每次MinorGC以後的存活對象數量能夠經過-XX:+PrintAdaptiveSizePolicy來查看。在下面的例子中,survivor對象的字節數是224408984。
 
2010-12-16T21:44:11.444-0600:   
[GCAdaptiveSizePolicy::compute_survivor_space_size_and_thresh:
survived: 224408984
promoted: 10904856
overflow: false
[PSYoungGen: 6515579K->219149K(9437184K)]
8946490K->2660709K(13631488K), 0.0725945 secs]
[Times: user=0.56 sys=0.00, real=0.07 secs]
[GCAdaptiveSizePolicy::compute_survivor_space_size_and_thresh:
survived: 224408984
promoted: 10904856
overflow: false
[PSYoungGen: 6515579K->219149K(9437184K)]
8946490K->2660709K(13631488K), 0.0725945 secs]
[Times: user=0.56 sys=0.00, real=0.07 secs]
 
   使用最大存活對象數量以及知道目標survivor空間的佔用量,你能夠決定出最差survivor空間大小,以使得讓對象老化得更加高效。若是目標survivor空間的佔用率沒有經過-XX:TargetSurvivorRatio=<percent>指定,那麼目標survivor空間的佔用率是50%。
 
   首先爲最差的場景優化survivor空間,這個須要找出在MinorGC以後最大的存活對象數量,注意能夠忽略應用進入穩定狀態前面的5到10個MinorGC。能夠經過awk或者perl腳原本完成這項工做。
 
   調整survivor空間的大小,不是僅僅修改survivor空間的大小以使得比存活的對象字節數更大那麼簡單。須要記住的是,若是不增長young代的空間大小,而增長survivor空間的大小,會減小eden空間的大小,這樣會致使頻繁的MinorGC,從而是的對象的老化速度加快,更快的進入old代,又會致使FullGC。因此,須要同步增長young代的空間大小。若是不增長old的空間,那麼就有可能形成頻繁的FullGC甚至內存溢出錯誤。所以,若是有能夠獲取的空間,須要同步增長Java堆的空間。
 
   一樣建議,HotSpot Vm使用默認的目標survivor空間佔用率(50%),若是使用了-XX:TargetSurvivorRatio=<percent>,會使用<percent>做爲MinorGC以後目標survivor空間佔用率。若是survivor空間的佔用率可能超過這個目標值,會在對象達到最大歲數以前把對象移動到old代去。
 
   經過一個例子詳細說明,考慮用下面的命令選項:
 
-Xmx13g -Xms13g -Xmn4g -XX:SurvivorRatio=6
-XX:+UseParallelOldGC -XX:-UseAdaptiveSizePolicy
-XX:PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintAdaptiveSizePolicy

 
 
   總共的Java堆空間是13g,young代是4g,old代是9g,survivor空間的大小是4g/(6+2)=512M。假如一個應用的存活對象是470M,因爲沒有明確指定-XX:TargetSurvivorRatio=<percent>,那麼默認的目標survivor空間佔用率是50%,那麼最小的survivor空間應該是940M,也就是最壞的狀況,須要設置940M的survivor空間。

   從上面的例子來看,4g的young代空間被分隔成兩個512M的survivor空間和一個3g的eden空間。剛纔分析的最壞狀況分配給survivor的空間是940M,差很少和1g至關。爲了保持對象老化速率,即保持MinorGC的頻率,eden空間須要保持在3g。所以,young代須要給每個survivor空間1g內存以及3g的eden空間,那麼young代須要增長到5g,也就是說young代須要增長1g空間,須要把-Xmn4g選項改爲-Xmn5g選項。比較的理想的狀況是,同步把Java堆的空間也增長1g。可是若是內存不夠用,須要保證old代空間大小至少是存活對象的1.5倍。
 
   假設應用的內存需求知足,增長survivor空間佔用後的命令選項是:
 
    
 -Xmx14g -Xms14g -Xmn5g -XX:SurvivorRatio=3
-XX:+UseParallelOldGC -XX:-UseAdaptiveSizePolicy
-XX:PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintAdaptiveSizePolicy

 
 
   old空間仍是9g,young代的空間是5g,比以前大了1g,eden仍是3g,每個survivor空間是1g。
 
   你可能須要重複屢次設定大小,直到知足內存佔用的條件下到達吞吐量的峯值。吞吐的峯值通常都是在對象最有效的老化的時候的達到的。
 
   一般的建議是,吞吐量垃圾回收器的垃圾回收的開銷應該小於5%。若是你只能把這個開銷下降1%甚至更少,你可能須要使用除本章描述以外的特別努力和很大的開銷來優化JVM。
  
優化Parallel GC線程
 
   吞吐量垃圾回收器的線程數的優化一樣基於有多少應用運行在同一個系統裏面以及硬件平臺。就像前面的「優化CMS」裏面提到的,若是是多個應用運行在同一個系統上面,建議使用比垃圾回收器默認使用的線程數更少的線程數,使用選項是-XX:ParallelGCThreads=<n>.
 
  另外,因爲大量的垃圾回收線程同時執行,垃圾回收可能會嚴重影響其餘應用的性能。因爲Java 6 Update 23以後,默認的垃圾回收線程是執行Runtime.availableProcessors()得到的,若是這個方法的返回值小於等於8,那麼就用這個返回值,若是比8更大,那麼就取這個值的5/8。若是運行多個應用,能夠根據應用的狀況來分配線程數,若是應用的消耗是至關的,那麼就用CPU的內核數除以應用數獲得每個應用能夠分配的線程。若是應用的load不至關,那麼就能夠根據應用的實際狀況來衡量和分配。
   
下一步
   若是你到這一步都尚未可以達到吞吐量的要求,那麼能夠嘗試後面的「額外的性能選項」,若是仍是沒法達到,就只能修改應用或者JVM部署結構了。若是進行了修改應用或者修改了部署結構,你須要從新作前面的各個步驟。
   可能會用到的一些邊緣場景,下面一節介紹。
邊緣問題

   在某些場景下,按照前面的一步步優化指導沒法產生效果。這一節說明一下這些狀況。
 
   一些應用分配了一些少許的很是大的長時間存活的對象。這樣的場景須要須要young代的空間比old代更大。
 
   一些應用會經歷不多的對象轉移。這樣的場景可能須要old代的空間遠遠大於存活對象的大小,因爲old的佔用量增加率很小。
 
   一些應用有小延遲需求,會使用CMS垃圾回收器,並且使用小young代空間(以至於MinorGC時間更短),以及大的old代空間。在這種配置下,對象會快速的從young代移動到old代,替代了高效老化對象。另外,CMS垃圾回收移動後的對象,碎片的可能性經過大的old代空間來解決。
 
   下一節介紹一些其餘的HotSpot VM選項來提高應用的性能。
 
其餘一些的性能命令行選項
 
   幾個可選的前面有提到的命令選項能夠用來提高Java應用的延遲和吞吐量性能,這些選項是經過JIT編譯器代碼優化以及其餘的HotSpot VM優化能力。下面介紹這些特性以及相適應的命令選項。
 
   最新和最大優化
   當新的性能優化集成到HotSpot VM中以後,能夠經過-XX:+AggressiveOpts選項來啓用。
 
   經過選項來引入新的優化,能夠把最新及最大的優化和以及通過長時間使用證實是穩定的優化分離開。應用一般更但願得到更好的穩定性,畢竟最新的優化可能會致使未知的問題。可是若是應用須要提高任何能夠提高的性能優化的時候,可使用命令選項來啓用這些優化。
 
   當新的優化被證實是穩定的以後,他們會被默認使用,也許須要升級幾個版本以後纔會變成默認。
 
   使用-XX:+AggressiveOpts命令選項以後,須要考慮到性能的提高,一樣也須要考慮到性能提高所帶來的不穩定風險。
 
   逃避分析
 
   逃避分析是一個種分析Java對象範圍的技術,在特殊狀況下,一個線程分配的對象可能被另一個線程使用,這個對象就叫着「逃避」。若是對象沒有逃避,額外的優化技術能夠應用,所以,這種優化技術叫作逃避分析。
 
   在HotSpot VM裏面的逃避分析優化能夠經過命令行選項:
 
 
   -XX:+DoEscapeAnalysis
   
   這是在Java 6 update 14中引入的,並且自動啓用經過-XX:+AggressiveOpts。在Java 6 update 23中是默認開啓的。
 
   經過逃避分析,HotSpot VM JIT編譯器,可應用下面的優化技術:
 
   一、對象爆炸:對象爆炸是一種對象的屬性存儲在Java堆之外並且可能潛在的消失。好比說,對象屬性能夠直接被放置到內存的寄存器裏面或者對象被分配棧裏面而不是堆裏面。
 
   分等級替換:分等級替換是一種用來減小內存使用的優化技術,考慮下面的Java類,表現爲保存長方形的長和寬:
public class Rectangle {
  int length;
  int width;
}
   HotSpot VM能夠優化內存分配和使用非逃避的Rectangle類的實例經過把長和寬都直接存儲到CPU的寄存器而不是分配Rectangle對象,結果是當時須要使用長和寬屬性的時候,不須要再複製到CPU的寄存器。這個能夠減小內存的讀取。
 
   二、線程棧分配:顧名思義,線程棧分配是一種把對象分配到線程棧中,而不是Java堆裏面的優化技術。一個對象永遠不逃避,就能夠放置到線程棧框架裏面,因爲沒有其餘線程須要看到這個對象。線程棧分配能夠減小對象分配到Java堆,能夠減小GC的頻率。
 
   三、消滅同步:若是線程分配的對象歷來不會逃避,並且這個線程鎖定了這個對象,這個鎖可能會被JIT編譯器消滅,畢竟沒有其餘線程會使用這個對象。
 
   四、消滅垃圾回收讀寫障礙:若是線程分配的對象歷來不會逃避,只會被當前線程使用,因此在其餘對象裏面存儲它的地址不須要障礙。讀或者寫障礙只有在對象會被其餘線程使用的時候纔有須要。
 
有偏見的鎖
 
   有偏見的鎖是使得鎖更偏心上次使用到它線程。在非競爭鎖的場景下,即只有一個線程會鎖定對象,能夠實現近乎無鎖的開銷。
 
   有偏見的鎖,是在Java 5 update 6引入的。經過HotSpot VM的命令選項-XX:+UseBiasedLocking啓用。
 
   Java 5 HotSpot JDK須要明確的命令來啓用這個特性,在使用-XX:+AggressiveOpts選項,有偏見的鎖會Java 5中會被自動啓用。在Java 6中是默認啓用的。
 
   各類經歷告訴咱們這個特性對大多數應用仍是很是有用的。而後,有一些應用使用這個屬性不必定可以表現的很好,好比,鎖被一般不被上次使用它的同一個線程使用。對於Java應用來講,因爲stop-the-world安全點操做須要取消偏見,這樣能夠經過使用-XX:-UseBiaseLocking來得到好處。若是你不清楚你的應用是什麼狀況,能夠經過分別設置這兩個選項來測試。
 
大頁面
 
   在計算機系統中,內存被分爲固定大小的區塊,這個區塊就叫作頁(page)。內存的存取是經過程序把虛擬內存地址轉換成物理內存地址實現的。虛擬到物理地址是在一個塊表裏面映射的。爲了減小每次存取內存的時候使用頁表的消耗,一般會使用一種快速的虛擬到物理地址轉換的緩存。這個緩存叫作轉換後備緩衝區(translation lookaside buffer),簡稱TLB。
 
   經過TLB來知足虛擬到物理地址的映射請求,會比遍歷頁表來找到映射關係快不少,一個TLB一般包含指定數量的條目。一個TLB條目是一個基於頁大小虛擬到物理地址映射,所以,更大的頁大小容許一個條目或者一個TLB有更大的內存地址範圍。在TLB中有更普遍的地址,更少的地址轉換請求在TLB中不命中,就能夠減小遍歷頁表(page table)操做。使用大頁的目的就是減小TLB的不命中。
 
   Oracle solariz,Linux 以及Windows都支持HotSpot VM使用大頁。一般處理器能夠支持幾種頁大小,不過不一樣的處理器各不相同。另外,操做系統配置須要使用大頁。
 
   下面說說怎麼樣在Linux下使用大頁(Large Page)
 
Linux下的大頁面
  在寫做本書的時候,在Linux下使用大頁,除使用-XX:+UseLargePages命令選項之外,須要修改操做系統配置。Linux的修改操做具體和發行版本以及內核有關係。爲了合理的啓用Linux下的大頁,能夠徵詢Linux管理員的意見或者閱讀Linux發行文檔。一旦使用了Linux操做系統配置已經修改,-XX:+UseLargePage命令行選項就必需要使用了。好比:
 
   $ java -server -Xmx1024m -Xms1024m -Xmn256m -XX:+UseLargePages ...
  若是大頁沒有被合理設置,HotSpot VM一樣會接受-XX:+UseLargePages是一個有效的選項,不過會報告沒法獲取大頁,並且會退回操做系統的默認頁大小。
相關文章
相關標籤/搜索