性能優化總結

  1. 根據現象進行分析

    性能瓶頸的表象:資源消耗過多、外部處理系統的性能不足、資源消耗不多但程序的響應速度卻仍達不到要求。

    1. 內存高

分析內存:系統的內存消耗過多往往有以下幾種原因:

  • 頻繁創建Java對象,如:數據庫查詢時,沒分頁,導致查出表中所有記錄;
  • 存在大對象,如:讀取文件時,不是邊讀邊寫而是先讀到一個byte數組,這樣如果讀取的文件時50M則僅此一項操作就會佔有JVM50M內存。
  • 存在內存泄漏,導致已不被使用的對象不被GC回收,從而隨着系統使用時間的增長,內存不斷受到解壓,最終OutOfMemory。

 

  1. 內存消耗分析(-Xms和-Xmx設爲相同的值,避免運行期JVM堆內存要不斷申請內存)

對於Java應用,內存的消耗主要在Java堆內存上,只有創建線程和使用Direct ByteBuffer纔會操作JVM堆外的內存。

JVM內存消耗過多會導致GC執行頻繁,CPU消耗增加,應用線程的執行速度嚴重下降,甚至造成OutOfMemoryError,最終導致Java進程退出。

  1. JVM堆外的內存:swap的消耗、物理內存的消耗、JVM內存的消耗。

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

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

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

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

       2)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狀況。

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

  充分利用內存:數據的緩存、耗時資源的緩存(數據庫連接創建、網絡連接的創建等)、頁面片段的緩存。畢竟內存的讀取肯定遠快於硬盤、網絡的讀取, 在內存消耗可接受、GC頻率、以及系統結構(例如集羣環境可能會帶來緩存的同步)可接受情況下,應充分利用內存來緩存數據,提升系統的性能。

內存消耗嚴重的解決方法

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

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

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

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

 

分析步驟:

  • 用TOP命令(或者用nmon)監控到JAVA程序佔用內存特別高, 然後用jmap -heap pid命令查看內存使用情況(老年代,年輕代等各個區內存設置和內存使用)。然後用jmap -histo pid打印輸出每個實例的內存使用。查看TOP20或者TOP30,可以分析出程序中消耗內存較多的實例。還可以用jmap -dump:live,file=a.map pid把java堆dump到本地進行分析,用jhat a.map命令通過html也沒進行JAVA堆分析。
  • 用top命令監控到JAVA程序內存比較高,用JVISUALVM對程序進行監控,並把堆DUMP下來,在jvisualvm中進行裝載,裝載後找到實例數最多或者佔用內存較多的實例。然後根據這個實例一步步往下詳細分析,找到實例中引用最頻繁的字段。
  • 程序內存佔用比較高,可以用jstat -gcutil pid 1000 5查看到內存的yougn gc以及FULL GC的頻率,消耗的時間。以及各個內存區所使用和分配的情況。根據各項指標進行內存的分析。還可以使用jstat -class pid查看加載的class數量,以及所佔空間信息。
  • jprofiler的memory views視圖中監控內存使用情況。通過實例數量和實例所佔內存大小進行分析。如果是內存泄露,可以對實例添加mark,程序運行一段時間後,進行GC,如果不進行垃圾回收,說明可能存在內存泄露問題。這時需要告訴開發把方法調用後,做置空操作。但是這個工具是十分消耗系統資源的。
    1. cpu高

    CPU消耗分析:CPU主要用於中斷、內核、用戶進程的任務處理,優先級爲中斷>內核>用戶進程。

 

  1. 上下文切換:

每個線程分配一定的執行時間,當到達執行時間、線程中有IO阻塞或高優先級線程要執行時,將切換執行的線程。在切換時要存儲目前線程的執行狀態,並恢復要執行的線程的狀態。

對於Java應用,典型的是在進行文件IO操作、網絡IO操作、鎖等待、線程Sleep時,當前線程會進入阻塞或休眠狀態,從而觸發上下文切換,上下文切換過多會造成內核佔據較多的CPU的使用。

 

  1. 運行隊列:

每個CPU核都維護一個可運行的線程隊列。系統的load主要由CPU的運行隊列來決定。

運行隊列值越大,就意味着線程會要消耗越長的時間才能執行完成。

 

  1. 利用率:

CPU在用戶進程、內核、中斷處理、IO等待、空閒,這五個部分使用百分比。

充分利用cpu: 在能並行處理的場景中未使用足夠的線程(線程增加:CPU資源消耗可接受且不會帶來激烈競爭鎖的場景下).

 

分析步驟:

  • 用top命令監控到程序的CPU佔用很高,於是用JVISUALVM的抽樣器對程序的CPU進行抽樣,抽樣完成後,在抽樣的快照里根據CPU時間進行降序排列,找到消耗CPU時間較長的方法。
  • 用top命令查找到佔用cpu最高的服務,然後用top命令1top -p pid-H單獨對子進程進行監視。找到佔用cpu最高的進程ID。要想找到哪段具體的代碼佔用了較多的CPU資源,可以用jstack打印棧信息到文件裏面。然後用jtgrep腳本把腳本中進程號的java線程抓出來。
  • 對程序加壓一段時間後,程序CPU消耗逐漸增高,可以用jprofiler工具進行監控。在cpu views視圖中對消耗CPU時間較長的方法進行逐步分析,查找到耗用cpu時間比較長的方法。
  •  

 

1.2.1程序調優方案

CPU消耗嚴重的解決方法

 

  1. CPU us高的解決方法:

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

 

    調優方案: 增加Thread.sleep,以釋放CPU 的執行權,降低CPU 的消耗。以損失單次執行性能爲代價的,但由於其降低了CPU 的消耗,對於多線程的應用而言,反而提高了總體的平均性能。(在實際的Java應用中類似場景, 對於這種場景最佳方式是改爲採用wait/notify機制)

    對於其他類似循環次數過多、正則、計算等造成CPU us過高的狀況, 則需要結合業務調優。對於GC頻繁,則需要通過JVM調優或程序調優,降低GC的執行次數。

 

  1. CPU sy高的解決方法:

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

 

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

1.2.2數據庫調優方案

    MySQL處在高負載環境下,磁盤IO讀寫過多,肯定會佔用很多資源,必然CPU會佔用過高。佔用CPU過高,可以做如下考慮:

    1.打開慢查詢日誌,查詢是否是某個SQL語句佔用過多資源,如果是的話,可以對SQL語句進行優化,比如優化 insert 語句、優化 group by 語句、優化 order by 語句、優化 join 語句等等;

    2.考慮索引問題;

    3.定期分析表,使用optimize table;show processlist;

    4.優化數據庫對象;

    5.考慮是否是鎖問題;

    6.調整一些MySQL Server參數,比如key_buffer_size、table_cache、innodb_buffer_pool_size、innodb_log_file_size等等;

   7.如果數據量過大,可以考慮使用MySQL集羣或者搭建高可用環境。

 

調優細節:http://coolshell.cn/articles/1846.html

          http://www.searchdatabase.com.cn/showcontent_76438.htm

 

    1. I/O高
  1. 文件IO消耗分析

Linux在操作文件時,將數據放入文件緩存區,直到內存不夠或系統要釋放內存給用戶進程使用。所以通常情況下只有寫文件和第一次讀取文件時會產生真正的文件IO。

對於Java應用,造成文件IO消耗高主要是多個線程需要進行大量內容寫入(例如頻繁的日誌寫入)的動作、磁盤設備本身的處理速度慢、文件系統慢、操作的文件本身已經很大。

 

  1. 網絡IO消耗分析

對於分佈式Java應用,網卡中斷是不是均衡分配到各CPU(cat/proc/interrupts查看)。

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

 

     從程序的角度而言,造成文件IO消耗嚴重的原因主要是多個線程在寫進行大量的數據到同一文件,導致文件很快變得很大,從而寫入速度越來越慢,並造成各線程激烈爭搶文件鎖。  調優方法:1)異步寫文件 2)批量讀寫 3)限流 4)限制文件大小

 

    從程序的角度而言,造成網絡IO消耗嚴重的原因主要是同時需要發送或接收的包太多。      調優方法:限流,限流通常是限制發送packet的頻率,從而在網絡IO消耗可接受的情況下來發送packget。

 

    1. 程序響應慢

    程序執行慢原因分析

 

  1. 鎖競爭激烈:很多線程競爭互斥資源,但資源有限, 造成其他線程都處於等待狀態。

 

  1. 未充分使用硬件資源:線程操作被串行化。

 

  1. 數據量增長:單表數據量太大(如1個億)造成數據庫讀寫速度大幅下降(操作此表)。

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

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

2)  使用併發包中的類:大多數採用了lock-free、nonblocking算法。使用Treiber算法:基於CAS以及AtomicReference。使用Michael-Scott非阻塞隊列算法:基於CAS以及AtomicReference,典型ConcurrentLindkedQueue。(基於CAS和AtomicReference來實現無阻塞是不錯的選擇,但值得注意的是,lock-free算法需不斷的循環比較來保證資源的一致性的,對於衝突較多的應用場景而言,會帶來更高的CPU消耗,因此不一定採用CAS實現無阻塞的就一定比採用lock方式的性能好。 還有一些無阻塞算法的改進:MCAS、WSTM等)

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

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

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

分析步驟:

  • 首先查看系統資源是不是使用正常,如果cpu比較高的話,可以查看sql語句,查看是不是存在union連接,是不是有索引,左右連接,iN等查詢效率比較低的sql.如果是兩個表進行聯合查詢,可用小表做驅動表,使性能得到提升。
  • 查看是否有線程池滿的現象,如果有,重啓tomcat能暫時解決這個問題,將不用的連接釋放,但是不能從根本上解決。需要驗證數據庫連接請求完成後,關閉這個請求連接。查看連接沒有被釋放的腳本:其中數據庫連接是有tomcat發佈地址。

declare cur_spid cursor 
for
select spid from sysprocesses where ipaddr='172.16.7.8'
go

declare @spid Integer
open  cur_spid
fetch cur_spid into @spid 
while @@sqlstatus=0
begin
        print '%1!' , @spid
  dbcc traceon(3604)
  dbcc sqltext(@spid )
  fetch cur_spid into @spid 
end
close cur_spid

 

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