摘要:在本文中我將會介紹應(yīng)用性能優(yōu)化的一般原則。性能優(yōu)化的流程圖摘取自和合著的性能,描述了應(yīng)用性能優(yōu)化的處理流程。例如,對每臺服務(wù)器,你面臨著為單個分配堆內(nèi)存和運行個并為每個分配堆內(nèi)存的選擇。不過位能使用堆內(nèi)存最大理論值只有。
原文鏈接:http://www.cubrid.org/blog/dev-platform/the-principles-of-java-application-performance-tuning/
本文是GC專家系列中的第五篇。在第一篇理解Java垃圾回收中我們學(xué)習(xí)了幾種不同的GC算法的處理過程,GC的工作方式,新生代與老年代的區(qū)別。所以,你應(yīng)該已經(jīng)了解了JDK 7中的5種GC類型,以及每種GC對性能的影響。
在第二篇Java垃圾回收的監(jiān)控中介紹了在真實場景中JVM是如何運行GC,如何監(jiān)控GC數(shù)據(jù)以及有哪些工具可用來方便進行GC監(jiān)控。
在第三篇GC 調(diào)優(yōu)中基于真實案例介紹了可用于GC調(diào)優(yōu)的最佳選項。同時也描述了如何通過降低移動到老年代中對象的數(shù)量來縮短Full GC耗時,以及如何設(shè)置GC類型及內(nèi)存大小。
在第四篇Apache的MaxClients設(shè)置及其對Tomcat Full GC的影響中介紹了Apache對MaxClients選項在系統(tǒng)發(fā)生GC時對整體性能的影響。
在本文中我將會介紹Java應(yīng)用性能優(yōu)化的一般原則。具體來說,我會介紹性能優(yōu)化的必要條件、判斷是否需要優(yōu)化的步驟,同時也會列出在性能優(yōu)化過程中經(jīng)遇到的一些問題。在文章結(jié)尾,我會給你一些在性能優(yōu)化過程中如何做出最優(yōu)決定的建議。
概述不是每個應(yīng)用都需要優(yōu)化。如果系統(tǒng)的運行狀況正如你的期望,你就沒必要花費更多精力在額外的性能提升上。然而,在調(diào)試過程中就期望系統(tǒng)能達到它的目標性能往往會比較困難。這時就需要做系統(tǒng)優(yōu)化的工作了。不管使用哪種語言,性能優(yōu)化都要有較高的專業(yè)技能和高度專注。另外,因為每個應(yīng)用都有自己獨特的操作和不同的資源使用情況,在優(yōu)化兩個不同系統(tǒng)中可能需要使用不同的具體方法。所以與開發(fā)應(yīng)用相比,性能優(yōu)化更需要有扎實的基礎(chǔ)知識,例如需要具有虛擬機、操作系統(tǒng)甚至計算機體系結(jié)構(gòu)的相關(guān)知識?;谶@些基礎(chǔ),再面對系統(tǒng)進行優(yōu)化時,成功的機率就會更高。
一些Java應(yīng)用的優(yōu)化只需要調(diào)整JVM的選項,例如改變垃圾回收類型,不過有時也是需要去調(diào)整源碼。不管使用哪種方式,你首先都需要去監(jiān)控Java應(yīng)用的執(zhí)行處理過程?;诖耍疚闹饕w的內(nèi)容如下:
如何監(jiān)控Java應(yīng)用
如何設(shè)置JVM選項
如何判斷是否有必要修改應(yīng)用代碼
Java性能優(yōu)化必備的基礎(chǔ)知識Java應(yīng)用在JVM中運行,因此優(yōu)化Java應(yīng)用,你需要理解JVM的運行過程。在前面的文章深入理解JVM你可以找到一些關(guān)于JVM重要概念的介紹。
在本文中關(guān)于JVM運行過程的講解著重于垃圾收集(GC)和 Hotspot相關(guān)知識。為了構(gòu)造一個使JVM 運行良好的環(huán)境,你需要理解操作如何為進程分配資源。所以即便是優(yōu)化Java應(yīng)用,你也需要像熟悉JVM一樣去熟悉操作系統(tǒng)甚至硬件知識。
與Java語言相關(guān)的知識也十分重要。同樣理解鎖和并發(fā)、熟悉類的加載與對象創(chuàng)建都是應(yīng)該具備的技能。
一旦將Java應(yīng)用優(yōu)化付諸行動,你就需要綜合利用上面提到的相關(guān)知識進行全面分析。
Java性能優(yōu)化的流程圖1摘取自Charlie Hunt和Binu John合著的《Java性能》,描述了Java應(yīng)用性能優(yōu)化的處理流程。
圖1: Java應(yīng)用性能優(yōu)化流程
上圖并不是一個一次性流程,在性能優(yōu)化完成之前你可能需要重復(fù)其中的過程。此過程同樣適用于如何選取一個期望的性能指標。在優(yōu)化過程中,有時需要降低性能指標的預(yù)期值,有時則需要提高性能指標的預(yù)期值。
JVM部署模型JVM部署模型關(guān)系到如何決定是否把應(yīng)用部署到單個或多個JVM上運行。這可以從系統(tǒng)的可用性、響應(yīng)速度和可維護性上來做取舍。即便是決定了使用多個JVM,你也還需要確定在單臺服務(wù)器上運行多個JVM或者是每臺服務(wù)器上運行一個JVM。例如,對每臺服務(wù)器,你面臨著為單個JVM分配8GB堆內(nèi)存和運行4個JVM并為每個JVM分配2GB堆內(nèi)存的選擇。當(dāng)然單臺服務(wù)器運行的JVM的數(shù)量也取決于CPU的核數(shù)以及應(yīng)用本身的特點。在對比以上兩個配置的響應(yīng)速度時,具有2GB堆空間的方案可能更有優(yōu)勢,因為使用2GB的堆空間比使用8GB堆空間在Full GC時耗時更短。不過話說回來,使用8GB堆空間卻可以減少Full GC的頻率。另外也可以通過提高應(yīng)用內(nèi)部緩存命中率的方式來提高系統(tǒng)響應(yīng)速度。所以,最終選擇部署模型需要綜合考慮應(yīng)用的特點和所選方案對應(yīng)用帶來的優(yōu)劣對比。
JVM體系結(jié)構(gòu)選擇JVM時還需要面臨32位JVM和64位JVM。同樣條件下,應(yīng)該優(yōu)化選擇32位JVM,因為32位JVM比64位的表現(xiàn)更優(yōu)。不過32位JVM能使用堆內(nèi)存最大理論值只有4GB。(事實上,32位操作系統(tǒng)和64位操作系統(tǒng)能分配的空間大小都只有2-3GB)。當(dāng)堆空間需求更大時,使用64位JVM會是更好的選擇。
表 1:性能對比
Benchmark | Time (sec) | Factor |
---|---|---|
C++ Opt | 23 | 1.0x |
C++ Dbg | 197 | 8.6x |
Java 64-bit | 134 | 5.8x |
Java 32-bit | 290 | 12.6x |
Java 32-bit GC* | 106 | 4.6x |
Java 32-bit SPEC GC* | 89 | 3.7x |
Scala | 82 | 3.6x |
Scala low-level* | 67 | 2.9x |
Scala low-level GC* | 58 | 2.5x |
Go 6g | 161 | 7.0x |
Go Pro* | 126 | 5.5x |
接下來要做的就是運行應(yīng)用并衡量其性能。這些過程包括GC調(diào)優(yōu)、調(diào)整操作系統(tǒng)設(shè)置以及修改應(yīng)用代碼。在這些過程中,你需要使用一些系統(tǒng)監(jiān)控工具或者程序分析工具來幫你完成任務(wù)。
值得注意的是為響應(yīng)速度的優(yōu)化和為吞吐量的優(yōu)化途徑可能會截然不同。例如,不時發(fā)生的stop-the-world會降低響應(yīng)速度,而Full GC則會導(dǎo)致單位時間內(nèi)的吞吐量量大幅減少。所以其中必定會有所權(quán)衡。當(dāng)然這些權(quán)衡不只發(fā)生于響應(yīng)速度和呑吐量之間,你可能需要使用更多的CPU資源來減少內(nèi)存使用來以避免響應(yīng)速度或吞吐量的降低。與此相反的場景也同樣會發(fā)生,你需要按一定的優(yōu)先順序來解決。
圖1中的性能優(yōu)化流程圖適用于包括Swing應(yīng)用在內(nèi)的幾乎所有Java應(yīng)用。盡管如此,這個流程并不太適用于我們NHN公司為網(wǎng)絡(luò)服務(wù)編寫服務(wù)器應(yīng)用的場景。下圖2是針對NHN公司并基于圖1制定的一個簡化的處理流程。
圖2:NHN公司的推薦的Java應(yīng)用優(yōu)化過程
上圖中的選擇JVM(Select JVM)是說通常32位JVM就足夠了,除非你需要使用JVM維護幾個GB的緩存數(shù)據(jù)。
好了,基于圖 2中的流程,你將開始學(xué)到處理每一步中所需應(yīng)對的事情。
JVM選項我將主要介紹如何為Web應(yīng)用服務(wù)器設(shè)置合適的JVM參數(shù)。盡管不能窮盡所有案例,但最優(yōu)的GC算法,尤其針對Web應(yīng)用,通常是CMS GC,這主要是因為Web應(yīng)用的低延遲要求決定的。當(dāng)然在使用CMS過程中,有時會遇到因為過多的內(nèi)存碎片導(dǎo)致的較長時間的stop-the-world現(xiàn)象發(fā)生。不過這個問題可以通過調(diào)整新生代大小或者碎片比例進行優(yōu)化。
設(shè)置新生代大小和設(shè)置整個堆大小一樣重要。最好通過-XX:NewRatio參數(shù)設(shè)置新生代空間與整個堆空間的大小比例,或者通過-XX:NewSize來多帶帶設(shè)置期望的新生代空間。設(shè)置新生代空間的重要性是因為大多數(shù)對象的存活時間很短。在Web應(yīng)用中,除了緩存之外的大多數(shù)對象,是在與HttpRequest相應(yīng)的HttpResponse創(chuàng)建的時候產(chǎn)生的,而這個過程很少會超過1秒,也就是說其中的對象的生命周期也不會超過1秒。如果新生代空間設(shè)置不夠大,當(dāng)需要創(chuàng)建新對象時,舊的對象就需要移到老年代。老年代的GC開銷卻比新生代GC開銷大得多,因此設(shè)置恰當(dāng)?shù)男律臻g是十分重要的。
盡管如此,如果新生代空間超過一定比例,系統(tǒng)的影響速度將會降低。因為新生代垃圾回收的基本過程就把對象從一個存活區(qū)(Survivor area)復(fù)制到另外一個存活區(qū)。所以像老年代一樣,在新生代執(zhí)行GC過程中也同樣會發(fā)生stop-the-world現(xiàn)象。如果新生代設(shè)置變大,存活區(qū)的空間相應(yīng)也會增加,結(jié)果就是需要復(fù)制的數(shù)據(jù)空間將增加。基于這些特點,根據(jù)操作系統(tǒng)不同,通過NewRatio選項為HotSpot JVM設(shè)置合適的新生代空間是很有必要的。
表2: 不同操作系統(tǒng)與JVM選項的NewRatio默認值
OS and option | Default -XX:NewRatio |
---|---|
Sparc -server | 2 |
Sparc -client | 8 |
x86 -server | 8 |
x86 -client | 12 |
如果設(shè)置了NewRatio,則將有1/(NewRatio + 1)的堆空間屬于新生代。你會發(fā)現(xiàn)上表中Sparc -server的NewRatio的值非常小,因為當(dāng)使用上面的默認值時,Sparc系統(tǒng)是用在比 x86更高端的場景中。因為x86性能的提升,目前使用x86 server也變得更為常見,像Sparc -server一樣設(shè)置NewRatio的值為2或3也更為合理。
除此之外,你也可以使用NewSize和MaxNewSize作為NewRatio的替代使用。新生代空間初始大小由NewSize設(shè)定,并且隨著內(nèi)存消耗,新生代空間最大可擴展到MaxNewSize的大小。隨著NewRatio的變化,Eden和Survivor區(qū)域的大小也在發(fā)生變化。正如通過相同-Xms和-Xmx為堆空間設(shè)置固定值,為新生代設(shè)置相同的MaxSize和MaxNewSize也是一個不錯的選擇。
如果同時設(shè)置了NewRatio和NewSize,其中較大的值會起作用。所以當(dāng)一個堆空間創(chuàng)建之后,就可以通過如下公式計算初始新生代空間的大?。?/p>
min(MaxNewSize, max(NewSize, heap/(NewRatio + 1)))
不過在優(yōu)化過程中,無乎不可能一下子就為堆大小和新生代大小找到了恰當(dāng)?shù)闹??;谖以贜HN運行Web應(yīng)用程序的經(jīng)驗,我推薦在啟動Java應(yīng)用時使用如下JVM選項。在經(jīng)過對這些選項的性能監(jiān)控結(jié)果分析之后,你會找到更合適的GC算法或選項。
表3:推薦的JVM選項
選項類型 | 選項 |
---|---|
運行模式 | -server |
堆大小 | 指定相同的-Xms和-Xmx |
新生代大小 | -XX:NewRatio: 取值在2-4之間 |
-XX:NewSize=?, -XX:MaxNewSize=?。使用NewSize替代NewRatio也是不錯的選擇 | |
永久代大小 | -XX:PermSize = 256m -XX:MaxPermSize=256m 把永久代大小設(shè)置為一個運行時不會出錯的大小,因為它并不影響系統(tǒng)的性能 |
GC 日志 | -Xloggc:$CATALANA_HOME/logs/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps。輸出GC日志并不明顯影響應(yīng)用性能,因此推薦保留詳細的GC日志信息。 |
GC 算法 | -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75。這只是一個推薦的通用配置。根據(jù)應(yīng)用特點不同,其他配置也許更優(yōu)。 |
OOM發(fā)生時輸出堆dump | -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$CATALINA_HOME/logs |
OOM發(fā)生后的執(zhí)行動作 | -XX:OnOutOfMemoryError=$CATALINA_HOME/bin/stop.sh 或者 -XX:OnOutOfMemoryError=$CATALINA_HOME/bin/restart.sh。OOM之后除了保留堆dump外,根據(jù)管理策略選擇合適的運行腳本。 |
需要獲取能反映應(yīng)用性能的幾個關(guān)鍵信息如下:
TPS(OPS):這個信息用于從概念上理解應(yīng)用的性能。
Request Per Second(RPS):嚴格來說,RPS并不同于響應(yīng)速度,但你可以把它理解為響應(yīng)速度。通過RPS,你可以檢查用戶獲取請求結(jié)果所耗費的時間。
RPS 標準偏差(RPS Standard Deviation):如果有可以,盡量保持RPS的穩(wěn)定。如果出現(xiàn)偏差,則需要檢查是否需要做GC優(yōu)化或者是否有內(nèi)部系統(tǒng)問題。
為了獲取盡可能精確的性能結(jié)果,首先要對應(yīng)用進行充分的預(yù)熱,待穩(wěn)定之后再開始性能測量,因為這時字節(jié)碼已被HotSpot JIT進行了編譯。通常,在使用nGrinder工具做負載測試時,至少要等系統(tǒng)達到某個負載水平10分鐘后再測量系統(tǒng)的實際性能。
在關(guān)鍵點上做優(yōu)化如果nGrinder的測試結(jié)果滿足預(yù)期,那就不需要對應(yīng)用進行優(yōu)化。如果性能遜于預(yù)期,則需要開始優(yōu)化以解決問題。下面通過具體案例來看性能優(yōu)化的方法。
Stop-the-World耗時過長長時間的stop-the-world通常是由于使用了不恰當(dāng)?shù)腉C選項或者不正確的應(yīng)用實現(xiàn)所致。通常可以通過分析工具(profiler)或者堆dump的結(jié)果判斷導(dǎo)致stop-the-world的原因。也就是說可以通過檢查堆中對象的類型和數(shù)量判斷問題原因。如果有過多非必須對象存在,則需要修改應(yīng)用代碼優(yōu)化實現(xiàn)。如果在創(chuàng)建對象過程中沒有明顯的問題,則需要調(diào)整GC選項。
為了把GC選項調(diào)整到恰當(dāng)?shù)脑O(shè)置,你需要有足夠長時間的GC日志,并從中找出在哪種狀況下出現(xiàn)了stop-the-world。關(guān)于選擇合適GC選項的具體細節(jié),可參考Java 垃圾回收的監(jiān)控。
CPU使用率過低當(dāng)系統(tǒng)發(fā)生阻塞時,TPS和CPU使用率都會降低。問題可能來自于內(nèi)部交互系統(tǒng)或者高并發(fā)。分析這種場景,可以對線程dump的結(jié)果進行分析或者使用分析工具(profiler)。線程dump的分析方法可以參考如何分析Java線程Dumps
使用一些商業(yè)分析工具(profiler),你可以得到非常具體的鎖相關(guān)的分析報告。不過,大多數(shù)場景只需要使用jvisualvm中的CPU分析器就可以獲得滿意的結(jié)果。
CPU使用率過高如果TPS很低,但CPU使用率卻非常高,就通常由于低效率的代碼實現(xiàn)所致。這種場景,也需要通過使用分析器找到瓶頸的位置。可用的分析工具有jvisuavm,Eclipse的TPTP或者使用JProbe。
優(yōu)化的途徑關(guān)于應(yīng)用優(yōu)化的一些建議途徑如下:
首先,判斷是否有必要做性能優(yōu)化。衡量系統(tǒng)的性能并非易事,任何時候都不能保證你能得到滿意的結(jié)果。所以如果應(yīng)用已經(jīng)達到了期望的目標性能,就沒必要投入精力做額外的優(yōu)化。
問題就在那里,你需要做的是解決它。Pareto 法則同樣適用于性能優(yōu)化。這并不是說一個特定的低性能表現(xiàn)只來源于一個問題,相反,在性能優(yōu)化過程中,更應(yīng)該把精力投入到對性能影響最大的那一點上。所以,當(dāng)解決了最嚴重的問題后,就可以接著處理其他問題。不過建議是每次只著重解決一個問題。
你可能想到了氣球效應(yīng)。為了實現(xiàn)一個目標,你需要決定放棄哪些。你可以通過使用緩存來提高響應(yīng)速度,然而隨著緩存的增加,其Full GC所需耗時也將增加。一般來說,如果你想維持少量的內(nèi)存使用,系統(tǒng)的呑吐量和響應(yīng)時間將會受到影響。所以,你要清楚哪些是最重要的,哪些微不足道的。
到目前為止,你已經(jīng)了解了Java應(yīng)用性能優(yōu)化的方法。為了介紹衡量性能的具體過程,我忽略了一些細節(jié)。盡管如此,我想本文已足夠應(yīng)對Java Web應(yīng)用的大多數(shù)優(yōu)化場景。
作者:Se Hoon Park,網(wǎng)絡(luò)平臺開發(fā)實驗室高級軟件工程師,NHN公司
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/65403.html
摘要:調(diào)優(yōu)調(diào)優(yōu)中基于真實案例介紹了可用于調(diào)優(yōu)的最佳選項。的設(shè)置及其對的影響的設(shè)置及其對的影響中介紹了對選項在系統(tǒng)發(fā)生時對整體性能的影響。具體來說,我會介紹性能優(yōu)化的必要條件判斷是否需要優(yōu)化的步驟,同時也會列出在性能優(yōu)化過程中經(jīng)遇到的一些問題。 1. 理解Java垃圾回收 理解Java垃圾回收中我們學(xué)習(xí)了幾種不同的GC算法的處理過程,GC的工作方式,新生代與老年代的區(qū)別。所以,你應(yīng)該已經(jīng)了解...
摘要:原文鏈接這是專家系列文章的第二篇。運行在本地虛擬機上的應(yīng)用的又稱為,通常與相同。性能數(shù)據(jù)需要持續(xù)觀察,因此在運行時需要定時輸出的監(jiān)控信息。新生代容量的統(tǒng)計信息。是提供的一個式的圖表監(jiān)控工具。 原文鏈接:http://www.cubrid.org/blog/dev-platform/how-to-monitor-java-garbage-collection/ 這是GC專家系列文章的第二...
摘要:原文鏈接本篇是專家系列的第三篇。但是,請記住調(diào)優(yōu)是不得已時的選擇??s短耗時的單次執(zhí)行與相比,耗時有較明顯的增加。創(chuàng)建文件過程中,進程會中斷,因此不要在正常運行時系統(tǒng)上做此操作。因此校驗結(jié)果并根據(jù)具體的服務(wù)需要,決定是否要進行調(diào)優(yōu)。 原文鏈接:http://www.cubrid.org/blog/dev-platform/how-to-tune-java-garbage-collecti...
摘要:本文是成為專家系列的第一篇。然而,在多線程環(huán)境下,將會有別樣的狀況。在中正是通過解決了多線程問題。在最后的并發(fā)清理階段,垃圾回收過程被真正執(zhí)行。在垃圾回收執(zhí)行過程中,其他線程依然在執(zhí)行。 原文鏈接:http://www.cubrid.org/blog/de... 了解Java的垃圾回收(GC)原理能給我們帶來什么好處?對于軟件工程師來說,滿足技術(shù)好奇心可算是一個,但重要的是理解GC能幫...
摘要:本文將介紹的參數(shù)的重要性以及在發(fā)生時對系統(tǒng)整體性能的顯著影響。我們來看下的選項在發(fā)生時會對系統(tǒng)帶來哪些影響。所以這些請求將會放到堆積隊列,隊列的長度是的中設(shè)置的。從而導(dǎo)致進程的數(shù)量超過,并觸發(fā)了操作系統(tǒng)進行內(nèi)存交換的閥值。 原文鏈接:http://www.cubrid.org/blog/dev-platform/maxclients-in-apache-and-its-effect-o...
閱讀 3206·2021-11-24 11:14
閱讀 3706·2021-11-22 15:22
閱讀 3360·2021-09-27 13:36
閱讀 877·2021-08-31 14:29
閱讀 1409·2019-08-30 15:55
閱讀 1874·2019-08-29 17:29
閱讀 1220·2019-08-29 16:24
閱讀 2591·2019-08-26 13:48