對於調優這個事情來講,通常就是三個過程:html
Java調優也不外乎這三步。java
此外,本文所講的性能分析、調優等是拋開如下因素的:ios
調優是須要作好準備工做的,畢竟每個應用的業務目標都不盡相同,性能瓶頸也不會總在同一個點上。在業務應用層面,咱們須要:git
此外,咱們還須要瞭解Java相關的一些知識:github
在系統層面可以影響應用性能的通常包括三個因素:CPU、內存和IO,能夠從這三方面進行程序的性能瓶頸分析。web
當程序響應變慢的時候,首先使用top、vmstat、ps等命令查看系統的cpu使用率是否有異常,從而能夠判斷出是不是cpu繁忙形成的性能問題。其中,主要經過us(用戶進程所佔的%)這個數據來看異常的進程信息。當us接近100%甚至更高時,能夠肯定是cpu繁忙形成的響應緩慢。通常說來,cpu繁忙的緣由有如下幾個:正則表達式
肯定好cpu使用率最高的進程以後就可使用jstack來打印出異常進程的堆棧信息:redis
jstack [pid]算法
接下來須要注意的一點是,Linux下全部線程最終仍是以輕量級進程的形式存在系統中的,而使用jstack只能打印出進程的信息,這些信息裏面包含了此進程下面全部線程(輕量級進程-LWP)的堆棧信息。所以,進一步的須要肯定是哪個線程耗費了大量cpu,此時可使用top -p [processId]來查看,也能夠直接經過ps -Le來顯示全部進程,包括LWP的資源耗費信息。最後,經過在jstack的輸出文件中查找對應的lwp的id便可以定位到相應的堆棧信息。其中須要注意的是線程的狀態:RUNNABLE、WAITING等。對於Runnable的進程須要注意是否有耗費cpu的計算。對於Waiting的線程通常是鎖的等待操做。數據庫
也可使用jstat來查看對應進程的gc信息,以判斷是不是gc形成了cpu繁忙。
jstat -gcutil [pid]
還能夠經過vmstat,經過觀察內核狀態的上下文切換(cs)次數,來判斷是不是上下文切換形成的cpu繁忙。
vmstat 1 5
此外,有時候可能會由jit引發一些cpu飈高的情形,如大量方法編譯等。這裏可使用-XX:+PrintCompilation這個參數輸出jit編譯狀況,以排查jit編譯引發的cpu問題。
對Java應用來講,內存主要是由堆外內存和堆內內存組成。
堆外內存
堆外內存主要是JNI、Deflater/Inflater、DirectByteBuffer(nio中會用到)使用的。對於這種堆外內存的分析,仍是須要先經過vmstat、sar、top、pidstat(這裏的sar,pidstat以及iostat都是sysstat軟件套件的一部分,須要單獨安裝)等查看swap和物理內存的消耗情況再作判斷的。此外,對於JNI、Deflater這種調用能夠經過Google-preftools來追蹤資源使用情況。
堆內內存
此部份內存爲Java應用主要的內存區域。一般與這部份內存性能相關的有:
以上使用不當很容易形成:
排查堆內存問題的經常使用工具是jmap,是jdk自帶的。一些經常使用用法以下:
此外,無論是使用jmap仍是在OOM時產生的dump文件,可使用Eclipse的MAT(MEMORY ANALYZER TOOL)來分析,能夠看到具體的堆棧和內存中對象的信息。固然jdk自帶的jhat也可以查看dump文件,會啓動web端口供開發者使用瀏覽器瀏覽堆內對象的信息。
一般與應用性能相關的包括:文件IO和網絡IO。
文件IO
可使用系統工具pidstat、iostat、vmstat來查看io的情況。這裏能夠看一張使用vmstat的結果圖。
這裏主要注意bi和bo這兩個值,分別表示塊設備每秒接收的塊數量和塊設備每秒發送的塊數量,由此能夠斷定io繁忙情況。進一步的能夠經過使用strace工具定位對文件io的系統調用。一般,形成文件io性能差的緣由不外乎:
網絡IO
查看網絡io情況,通常使用的是netstat工具。能夠查看全部鏈接的情況、數目、端口信息等。例如:當time_wait或者close_wait鏈接過多時,會影響應用的相應速度。
netstat -anp
此外,還可使用tcpdump來具體分析網絡io的數據。固然,tcpdump出的文件直接打開是一堆二進制的數據,可使用wireshark閱讀具體的鏈接以及其中數據的內容。
tcpdump -i eth0 -w tmp.cap -tnn dst port 8080 #監聽8080端口的網絡請求並打印日誌到tmp.cap中
還能夠經過查看/proc/interrupts來獲取當前系統使用的中斷的狀況。
各個列依次是:
irq的序號, 在各自cpu上發生中斷的次數,可編程中斷控制器,設備名稱(request_irq的dev_name字段)
經過查看網卡設備的終端狀況能夠判斷網絡io的情況。
上面分別針對CPU、內存以及IO講了一些系統/JDK自帶的分析工具。除此以外,還有一些綜合分析工具或者框架能夠更加方便咱們對Java應用性能的排查、分析、定位等。
VisualVM
這個工具應該是Java開發者們很是熟悉的一款java應用監測工具,原理是經過jmx接口來鏈接jvm進程,從而可以看到jvm上的線程、內存、類等信息。 若是想進一步查看gc狀況,能夠安裝visual gc插件。此外,visualvm也有btrace的插件,能夠可視化直觀的編寫btrace代碼並查看輸出日誌。 與VisualVm相似的,jconsole也是經過jmx查看遠程jvm信息的一款工具,更進一步的,經過它還能夠顯示具體的線程堆棧信息以及內存中各個年代的佔用狀況,也支持直接遠程執行MBEAN。固然,visualvm經過安裝jconsole插件也能夠擁有這些功能。但因爲這倆工具都是須要ui界面的,所以通常都是經過本地遠程鏈接服務器jvm進程。服務器環境下,通常並不用此種方式。
Java Mission Control(jmc)
此工具是jdk7 u40開始自帶的,原來是JRockit上的工具,是一款採樣型的集診斷、分析和監控與一體的很是強大的工具。https://docs.oracle.com/javacomponents/jmc-5-5/jmc-user-guide/toc.htm
Btrace
這裏不得不提的是btrace這個神器,它使用java attach api+ java agent + instrument api可以實現jvm的動態追蹤。在不重啓應用的狀況下能夠加入攔截類的方法以打印日誌等。具體的用法能夠參考Btrace入門到熟練小工徹底指南。
Jwebap
Jwebap是一款JavaEE性能檢測框架,基於asm加強字節碼實現。支持:http請求、jdbc鏈接、method的調用軌跡跟蹤以及次數、耗時的統計。由此能夠獲取最耗時的請求、方法,並能夠查看jdbc鏈接的次數、是否關閉等。但此項目是2006年的一個項目,已經將近10年沒有更新。根據筆者使用,已經不支持jdk7編譯的應用。若是要使用,建議基於原項目二次開發,同時也能夠加入對redis鏈接的軌跡跟蹤。固然,基於字節碼加強的原理,也能夠實現本身的JavaEE性能監測框架。
上圖來自筆者公司二次開發過的jwebap,已經支持jdk8和redis鏈接追蹤。
useful-scripts
這裏有一個本人蔘與的開源的項目:https://github.com/superhj1987/useful-scripts,封裝了不少經常使用的性能分析命令,好比上文講的打印繁忙java線程堆棧信息,只須要執行一個腳本便可。
與性能分析相對應,性能調優一樣分爲三部分。
此外,使用多線程的時候,還須要注意如下幾點:
內存的調優主要就是對jvm的調優。
其中,對於第一點,具體的還有一點建議:
通常吞吐量優先的應用都應該有一個很大的年輕代和一個較小的年老代。這樣能夠儘量回收掉大部分短時間對象,減小中期的對象,而年老代存放長期存活對象。
此外,較小堆引發的碎片問題:由於年老代的併發收集器使用標記、清除算法,因此不會對堆進行壓縮。當收集器回收時,會把相鄰的空間進行合併,這樣能夠分配給較大的對象。可是,當堆空間較小時,運行一段時間之後,就會出現「碎片」,若是併發收集器找不到足夠的空間,那麼併發收集器將會中止,而後使用傳統的標記、清除方式進行回收。若是出現「碎片」,可能須要進行以下配置:-XX:+UseCMSCompactAtFullCollection,使用併發收集器時,開啓對年老代的壓縮。同時使用-XX:CMSFullGCsBeforeCompaction=xx設置多少次Full GC後,對年老代進行壓縮。
其他對於jvm的優化問題可見後面JVM參數進階一節。
代碼上,也須要注意:
不要用Log4j輸出文件名、行號,由於Log4j經過打印線程堆棧實現,生成大量String。此外,使用log4j時,建議此種經典用法,先判斷對應級別的日誌是否打開,再作操做,不然也會生成大量String。
if (logger.isInfoEnabled()) { logger.info(msg); }
文件IO上須要注意:
網絡IO上須要注意:
此外,jdk七、8在jvm的性能上作了一些加強:
此外,網上還有不少過期的建議,不要再盲目跟隨:
jvm的參數設置一直是比較理不清的地方,不少時候都搞不清都有哪些參數能夠配置,參數是什麼意思,爲何要這麼配置等。這裏主要針對這些作一些常識性的說明以及對一些容易讓人進入陷阱的參數作一些解釋。
如下全部都是針對Oracle/Sun JDK 6來說
啓動參數默認值
Java有不少的啓動參數,並且不少版本都並不同。可是如今網上充斥着各類資料,若是不加辨別的所有使用,不少是沒有效果或者原本就是默認值的。通常的,咱們能夠經過使用java -XX:+PrintFlagsInitial來查看全部能夠設置的參數以及其默認值。也能夠在程序啓動的時候加入-XX:+PrintCommandLineFlags來查看與默認值不相同的啓動參數。若是想查看全部啓動參數(包括和默認值相同的),可使用-XX:+PrintFlagsFinal。
輸出裏「=」表示使用的是初始默認值,而「:=」表示使用的不是初始默認值,多是命令行傳進來的參數、配置文件裏的參數或者是ergonomics自動選擇了別的值。
此外,還可使用jinfo命令顯示啓動的參數。
這裏須要指出的是,當你配置jvm參數時,最好是先經過以上命令查看對應參數的默認值再肯定是否須要設置。也最好不要配置你搞不清用途的參數,畢竟默認值的設置是有它的合理之處的。
動態設置參數
當Java應用啓動後,定位到了是GC形成的性能問題,可是你啓動的時候並無加入打印gc的參數,不少時候的作法就是從新加參數而後重啓應用。但這樣會形成必定時間的服務不可用。最佳的作法是可以在不重啓應用的狀況下,動態設置參數。使用jinfo能夠作到這一點(本質上仍是基於jmx的)。
jinfo -flag [+/-][flagName] [pid] #啓用/禁止某個參數 jinfo -flag [flagName=value] [pid] #設置某個參數
對於上述的gc的狀況,就可使用如下命令打開heap dump並設置dump路徑。
jinfo -flag +HeapDumpBeforeFullGC [pid] jinfo -flag +HeapDumpAfterFullGC [pid] jinfo -flag HeapDumpPath=/home/dump/dir [pid]
一樣的也能夠動態關閉。
jinfo -flag -HeapDumpBeforeFullGC [pid] jinfo -flag -HeapDumpAfterFullGC [pid]
其餘的參數設置相似。
-verbose:gc 與 -XX:+PrintGCDetails
不少gc推薦設置都同時設置了這兩個參數,其實,只要打開了-XX:+PrintGCDetails,前面的選項也會同時打開,無須重複設置。
-XX:+DisableExplicitGC
這個參數的做用就是使得system.gc變爲空調用,不少推薦設置裏面都是建議開啓的。可是,若是你用到了NIO或者其餘使用到堆外內存的狀況,使用此選項會形成oom。能夠用XX:+ExplicitGCInvokesConcurrent或XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses(配合CMS使用,使得system.gc觸發一次併發gc)代替。
此外,還有一個比較有意思的地方。若是你不設置此選項的話,當你使用了RMI的時候,會週期性地來一次full gc。這個現象是因爲分佈式gc形成的,爲RMI服務。具體的可見此連接內容中與dgc相關的:http://docs.oracle.com/javase/6/docs/technotes/guides/rmi/sunrmiproperties.html
MaxDirectMemorySize
此參數是設置的堆外內存的上限值。當不設置的時候爲-1,此值爲-Xmx減去一個survivor space的預留大小。
因爲遺留緣由,做用相同的參數
-XX:MaxTenuringThreshold
使用工具查看此值默認值爲15,可是選擇了CMS的時候,此值會變成4。當此值設置爲0時,全部eden裏的活對象在經歷第一次minor GC的時候就會直接晉升到old gen,survivor space直接就沒用。
-XX:HeapDumpPath
使用此參數能夠指定-XX:+HeapDumpBeforeFullGC、-XX:+HeapDumpAfterFullGC、-XX:+HeapDumpOnOutOfMemoryError觸發heap dump文件的存儲位置。