天天看點

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是典型實作,好處是可以明顯提升讀的性能,适合讀多寫少的場景, 但由于寫操作每次都要複制一份對象,會消耗更多的記憶體。

本文轉自 sykmiao 51CTO部落格,原文連結:http://blog.51cto.com/syklinux/1942316,如需轉載請自行聯系原作者