Java性能調優

JVM調優(最關鍵參數爲:-Xms -Xmx -Xmn -XX:SurvivorRatio -XX:MaxTenuringThreshold)

 

代大小調優:

避免新生代大小設置過小、避免新生代大小設置過大、避免Survivor設置過小或過大、合理設置新生代存活週期。

-Xmn 調整新生代大小,新生代越大通常也意味着更多對象會在minor GC階段被回收,但可能有可能造成舊生代大小,造成頻繁觸發Full GC,甚至是OutOfMemoryError。

-XX:SurvivorRatio調整Eden區與Survivor區的大小,Eden 區越大通常也意味着minor GC發生頻率越低,但可能有可能造成Survivor區太小,導致對象minor GC後就直接進入舊生代,從而更頻繁觸發Full GC。

 

GC策略的調優:CMS GC多數動作是和應用併發進行的,確實可以減小GC動作給應用造成的暫停時間。對於Web應用非常需要一個對應用造成暫停時間短的GC,再加上Web應用 的瓶頸都不在CPU上,在G1還不夠成熟的情況下,CMS GC是不錯的選擇。

(如果系統不是CPU密集型,且從新生代進入舊生代的大部分對象是可以回收的,那麼採用CMS GC可以更好地在舊生代滿之前完成對象的回收,更大程度降低Full GC發生的可能)

 

在調整了內存管理方面的參數後應通過-XX:PrintGCDetails、-XX:+PrintGCTimeStamps、 -XX:+PrintGCApplicationStoppedTime以及jstat或visualvm等方式觀察調整後的GC狀況。

出內存管理以外的其他方面的調優參數:-XX:CompileThreshold、-XX:+UseFastAccessorMethods、 -XX:+UseBaiasedLocking。

 

 

 

程序調優 

CPU消耗嚴重的解決方法

CPU us高的解決方法:

CPU us 高的原因主要是執行線程不需要任何掛起動作,且一直執行,導致CPU 沒有機會去調度執行其他的線程。

調優方案: 增加Thread.sleep,以釋放CPU 的執行權,降低CPU 的消耗。以損失單次執行性能爲代價的,但由於其降低了CPU 的消耗,對於多線程的應用而言,反而提高了總體的平均性能。

(在實際的Java應用中類似場景, 對於這種場景最佳方式是改爲採用wait/notify機制)

對於其他類似循環次數過多、正則、計算等造成CPU us過高的狀況, 則需要結合業務調優。

對於GC頻繁,則需要通過JVM調優或程序調優,降低GC的執行次數。

CPU sy高的解決方法:

CPU sy 高的原因主要是線程的運行狀態要經常切換,對於這種情況,常見的一種優化方法是減少線程數。

調優方案: 將線程數降低

這種調優過後有可能會造成CPU us過高,所以合理設置線程數非常關鍵。

 

對於Java分佈式應用,還有一種典型現象是應用中有較多的網絡IO操作和確實需要一些鎖競爭機制(如數據庫連接池),但爲了能夠支撐搞得併發量,可採用協程(Coroutine)來支撐更高的併發量,避免併發量上漲後造成CPU sy消耗嚴重、系統load迅速上漲和系統性能下降。

在Java中實現協程的框架有Kilim,Kilim執行一項任務創建Task,使用Task的暫停機制,而不是Thread,Kilim承擔了線程調度以及上下切換動作,Task相對於原生Thread而言就輕量級多了,且能更好利用CPU。Kilim帶來的是線程使用率的提升,但同時由於要在JVM堆中保存Task上下文信息,因此在採用Kilim的情況下要消耗更多的內存。(目前JDK 7中也有一個支持協程方式的實現,另外基於JVM的Scala的Actor也可用於在Java使用協程)

 

文件IO消耗嚴重的解決方法

從程序的角度而言,造成文件IO消耗嚴重的原因主要是多個線程在寫進行大量的數據到同一文件,導致文件很快變得很大,從而寫入速度越來越慢,並造成各線程激烈爭搶文件鎖。

常用調優方法:

異步寫文件

批量讀寫

限流

限制文件大小

 

內存消耗嚴重的解決方法

釋放不必要的引用:代碼持有了不需要的對象引用,造成這些對象無法被GC,從而佔據了JVM堆內存。(使用ThreadLocal:注意在線程內動作執行完畢時,需執行ThreadLocal.set把對象清除,避免持有不必要的對象引用)

使用對象緩存池:創建對象要消耗一定的CPU以及內存,使用對象緩存池一定程度上可降低JVM堆內存的使用。

採用合理的緩存失效算法:如果放入太多對象在緩存池中,反而會造成內存的嚴重消耗, 同時由於緩存池一直對這些對象持有引用,從而造成Full GC增多,對於這種狀況要合理控制緩存池的大小,避免緩存池的對象數量無限上漲。(經典的緩存失效算法來清除緩存池中的對象:FIFO、LRU、LFU等)

合理使用SoftReference和WeekReference:SoftReference的對象會在內存不夠用的時候回收,WeekReference的對象會在Full GC的時候回收。

資源消耗不多但程序執行慢的情況的解決方法

降低鎖競爭: 多線多了,鎖競爭的狀況會比較明顯,這時候線程很容易處於等待鎖的狀況,從而導致性能下降以及CPU sy上升。

使用併發包中的類:大多數採用了lock-free、nonblocking算法。

使用Treiber算法:基於CAS以及AtomicReference。

使用Michael-Scott非阻塞隊列算法:基於CAS以及AtomicReference,典型ConcurrentLindkedQueue。

(基於CAS和AtomicReference來實現無阻塞是不錯的選擇,但值得注意的是,lock-free算法需不斷的循環比較來保證資源的一致性的,對於衝突較多的應用場景而言,會帶來更高的CPU消耗,因此不一定採用CAS實現無阻塞的就一定比採用lock方式的性能好。 還有一些無阻塞算法的改進:MCAS、WSTM等)

儘可能少用鎖:儘可能只對需要控制的資源做加鎖操作(通常沒有必要對整個方法加鎖,儘可能讓鎖最小化,只對互斥及原子操作的地方加鎖,加鎖時儘可能以保護資源的最小化粒度爲單位--如只對需要保護的資源加鎖而不是this)。

拆分鎖:獨佔鎖拆分爲多把鎖(讀寫鎖拆分、類似ConcurrentHashMap中默認拆分爲16把鎖),很多程度上能提高讀寫的性能,但需要注意在採用拆分鎖後,全局性質的操作會變得比較複雜(如ConcurrentHashMap中size操作)。(拆分鎖太多也會造成副作用,如CPU消耗明顯增加)

去除讀寫操作的互斥:在修改時加鎖,並複製對象進行修改,修改完畢後切換對象的引用,從而讀取時則不加鎖。這種稱爲CopyOnWrite,CopyOnWriteArrayList是典型實現,好處是可以明顯提升讀的性能,適合讀多寫少的場景, 但由於寫操作每次都要複製一份對象,會消耗更多的內存。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章