本文是GC專家系列中的第四篇。在第一篇理解Java垃圾回收中咱們學習了幾種不一樣的GC算法的處理過程,GC的工做方式,新生代與老年代的區別。因此,你應該已經瞭解了JDK 7中的5種GC類型,以及每種GC對性能的影響。apache
在第二篇Java垃圾回收的監控中介紹了在真實場景中JVM是如何運行GC,如何監控GC數據以及有哪些工具可用來方便進行GC監控。segmentfault
在第三篇GC 調優中基於真實案例介紹了可用於GC調優的最佳選項。同時也描述瞭如何經過下降移動到老年代中對象的數量來縮短Full GC耗時,以及如何設置GC類型及內存大小。tomcat
本文將介紹Apache的MaxClients
參數的重要性以及在GC發生時對系統總體性能的顯著影響。經過幾個例子,你將會更清晰的理解MaxClients
值所引起的問題。最後會介紹如何依據系統的可用內存來爲MaxClients
設置合理的數值。服務器
NHN的服務運行環境中有大量的流控(Throttle valve)選項,這些選項對系統的穩定運行具備重要做用。咱們來看下Apache的MaxClients
選項在Tomcat發生Full GC時會對系統帶來哪些影響。socket
大部分的開發人員都知道GC 發生中會伴隨着"stop the world(STW)現象"(具體詳情參考理解Java垃圾回收)。尤爲是NHN的Java開發人員可能都經歷過在Tomcat中由GC相關問題而致使的系統崩潰。由於JVM管理內存,所以Java應用系統不可避免的會遇到GC引發的STW現象。工具
在你開發的線上系統中,GC天天都會發生不少次。在GC發生時,即使TTS沒有發生,卻依然可能會給用戶503的錯誤響應。性能
根據結構特色,Web服務更適合於作橫向擴展而非單純的提升單一機器的性能。因此一般根據性能須要,Web服務的服務器部署結構由一臺Apache服務器和多臺Tomcat服務器組成。在本文中,假設一個Apache服務和Tomcat服務部署在同一臺物理主機上,以下圖所示:學習
圖1: 本文假設的服務運行環境spa
做爲參考,本文所述參數均是基於Apache 2.2.21(prefork MPM),Tomcat 6.0.35,jdk 1.6.0_24,並運行在CentOS 4.7.2(32位)操做系統上。
系統內存2GB,並使用ParallelOldGC垃圾回收,默認開啓了AdaptiveSizePolicy
選項並設置堆大小爲600MB。
假設Apache的流量爲200QPS,並開啓10個httpd處理進程(儘管實際場景依賴於請求的響應時間)。在這種前提下,假設full GC致使的停頓耗時1秒,若是Tomcat發生了Full GC將會怎麼樣?
首先你能想到的是full GC致使Tomcat停頓,處理中的請求將得不到響應。若是這樣,Tomcat暫停,請求得不處處理,Apache將會怎麼樣?
即便Tomcat因Full GC而暫停處理,而請求卻仍以200 req/s的速度到達Apache。在full GC發生前,只須要10個或者稍微多一點的httpd進程就能夠快速響應服務請求。可是如今Tomcat暫停了,爲了處理新的請求Apache將持續建立新的httpd進程直到httpd.conf文件中定義的MaxClients
閥值。由於MaxClients
默認值爲256,因此200 req/s的請求並不會帶來太大問題。
這個時候,新建立的httpd 進程會怎麼樣?
Httpd 進程使用mod_jk模塊管理的AJP鏈接池中的空閒鏈接把請求發送到Tomcat。若是沒有空閒鏈接,則會要求建立新的鏈接。然而由於Tomcat處理暫停狀態,新建鏈接的請求將被拒絕。因此這些請求將會放到堆積隊列(backlog queue),隊列的長度是server.xml的AJP Connector中設置的。
若是請求數據超出了堆積隊列的長度,Apache將會收到鏈接拒絕錯誤,並把這個錯誤以HTTP 503的方式返回給用戶。
在本例的中,堆積隊列的長度默認設置爲100,而請求速度爲200 req/s,所以在由full GC致使Tomcat暫停的這1秒中,將有超過100的請求將會收到503錯誤。
Full GC結束以後,堆積隊列中的socket鏈接會被Tomcat接收並分配給工做線程(最大工做線程數由MaxThreads
決定,默認值爲200)來處理請求。
在上面的場景中,如何設置才能避免給用戶返回503錯誤?
首先咱們須要知道,應該設置足夠的堆積隊列長度以容納在Tomcat Full GC致使的暫停期間流入的請求。所以堆積隊列最小長度至少爲200(上文中QPS爲200)。
這樣配置之後,是否還有其餘問題?
把堆積隊列長度設置爲200後,咱們再次重複上面的場景。結果問題卻比以前更加嚴重。
正常狀況下系統內存使用量維持在50%,而在發生Full GC時內存使用卻迅速上升到100%,引發內存交換區(swap)使用量的極劇增長。更爲嚴重的是Full GC致使的響應停頓由原來的1秒增長到了4秒,直接後果就是期間系統像掛掉了同樣,不能響應任何請求。
在以前的場景中,只有100左右的請求會收到 503 的錯誤,而增長堆積隊列到200後卻致使了500甚至更多的請求被掛起至少3秒不能收到任何響應。
這個例子很好的證實了若是不能準備的理清配置信息之間的因果關係,可能會對系統帶來極爲嚴重的影響。
爲何會這樣?
原理就是要清楚MaxClients
選項的特性。
MaxClients
的值不易設置過大,設置MaxClients
的關鍵在於即使建立了MaxClients
數量的httpd進程,也要須要維持應用系統的內存使用量不該超過80%。
系統交換區默認值爲60,所以若是內存使用超過80%,系統將會發生頻繁的內存交換。
咱們再來看下爲何這個特性會致使上面所述的嚴重後果。
當請求的QPS爲200時,Tomcat會被Full GC暫停響應,而後把堆積列隊容量設置爲200。起初大約有100個額外的httpd 進程會被Apache建立,緊接着內存使用量超過了80%,引發操做系統主動的使用交換區的內存空間,而因GC存活在JVM老年代中的對象被操做系統誤認爲長時間未使用,從而致使這些對象被移動到交換區。
最後,當GC過程當中涉及到交換區時,耗時就會迅速增長。然後httpd進程數繼續增長,致使內存使用量達到了100%,從而出現了上述的嚴重後果。
上述案例的先後區別僅在於堆積隊列的長度:100和200。但爲何在200時會出現更嚴重的情況?
緣由是堆積隊列不一樣的長度致使了httpd進程數的不一樣。當值爲100時,在發生Full GC時100個請求所要求建立的鏈接被置於堆積隊列中。再有新的請求會被拒絕並返回503錯誤,因此係統的整個httpd的進程數僅超出100不多的數量。
但當隊列長度設置爲200時,有200個請求被接收並置於隊列中。從而致使httpd進程的數量超過200,並觸發了操做系統進行內存交換的閥值。
因此,若是不顧內存使用狀況而一味的加大MaxClients
的數值,將會致使Full GC時httpd進程數迅速增長,引進內存交換並最終下降系統的總體性能。
因此如何設置MaxClients,如何找到當前系統的閥值?
若是系統總內存爲2GB,設置MaxClient
的值須要保證在任什麼時候候內存的使用量不超過80%即1.6GB,從而避免因內存交換致使的性能降低。也就是說僅有1.6GB空間供Apache, Tomcat和其餘默認安裝的代理程序共享和分配內存。
假如默認安裝的代理程序佔用200M內存;Tomcat的堆空間設置-Xmx
爲600M,以下圖所示,Tomcat總佔用量將725M (持久代 + 本地堆空間)。Apache可以使用的空間爲剩下的700M。
圖 2:Top命令的截圖
對於Apache的700M內存,該如何設置合理的MaxClients值?
固然這也取決於Apache加載的模塊類型和數量。以NHN的Web服務爲例,把Apache看成簡單的代理使用,根據上圖RES顯示,4M空間對於每一個httpd進程來講已足夠使用。所以700M空間能設置的MaxClients
爲175。
可靠的服務配置要可以在滿載的狀況降低低系統停頓時間並可以最大範圍的保證成功響應用戶請求。對於Java應用來講,必需要確認在Full GC引發的SWT狀況下,系統的配置是否可以提供足夠可靠的服務。
若是爲了應對單純的請求增長和防止DDos攻擊,在不考慮內存使用的狀況下把MaxClients
設置過大,那麼MaxClients
不但會失去做爲流控的用途,反而會帶來更爲嚴重的後果。
在這個案例中,解決問題的最優途徑是加大系統的內存,或者設置MaxClients
爲175(上面的計算結果)以保證只有QPS超過175時纔會出現503錯誤。
做者:Dongsoon Choi,遊戲服務技術支持團隊高級工程師,NHN公司