前言
現實企業級
Java項目中,有時候會碰到下面這些問題:
●OutOfMemoryError,內存不足
●內存泄露
●線程死鎖
●鎖爭用(Lock Contention)
●Java進程消耗CPU過高
●......
這些問題在日常開發中可能被很多人忽視(比如有的人遇到上面的問題只是重啟服務器或者調大內存,而不會深究問題根源),但作為一名優秀的
性能測試工程師,必須能夠定位並分析這些問題。那么在性能測試的整個流程當中,如何來監控這些問題呢?這里介紹如何使用 JVisualVM 進行性能監控分析。
jvisualvm 安裝
JDK中還藏着一個寶貝,它的名字叫做VisualVM,自從 JDK 6 Update 7 以后已經作為
Oracle JDK 的一部分,它主要用來監控JVM的運行情況,可以用它來查看和瀏覽Heap Dump、Thread Dump、內存對象實例情況、GC執行情況、CPU消耗以及類的裝載情況。該工具位於 JDK 根目錄的 bin 文件夾下,無需安裝,正常安裝完jdk后,至jdk的bin目錄下直接運行jvisualvm.exe即可。
jvisualvm 插件安裝,選擇【工具】——【插件】——【可用插件】,打開后如下圖,沒有檢測到可用插件
然后點擊檢測最新版本,提示無法連接到java visualvm插件中心,這是因為java.net網站已經被Oracle關閉了,visualvm已經遷移到了github上,地址是https://visualvm.github.io/index.html,點擊Plugins進入插件頁面
因為我的jdk是1.7.0_80,所以找到對應插件更新地址https://visualvm.github.io/archive/uc/8u40/updates.xml.gz
再次回到jvisualvm-》工具-》插件,找到設置
把紅色框中的url地址改成剛才找到的https://visualvm.github.io/archive/uc/8u40/updates.xml.gz,修改之后,更新可用插件,安裝即可。重啟jvisualvm。
jvisualvm遠程監控
jvisualvm可以監控所有的java進程,本地機器的程序直接可以監聽到,遠程機器的程序需要加上JVM參數,下面以監控遠程tomcat為例介紹如何添加遠程監控。
1、遠程服務器配置
在tomcat/bin/catalina.sh加入以下配置:
JAVA_OPTS="-Djava.rmi.server.hostname=10.1.60.63 -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"
端口號可以自己定義,如果沖突了,修改下就好了。配置好后,重啟tomcat。那問題又來了,jar包如何配置監聽呢?比如我們的前置機。其實很簡單,只要啟動jar包時加上上面的配置參數就好了。命令如下:nohup java -jar Djava.rmi.server.hostname=192.168.1.24 -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false /home/webapps/test_front_end_service/front-end-service.jar &
2、在jvisualvm中進行遠程配置
具體如下:
到此遠程監控添加完成了,在遠程主機下會有一個jmx連接,打開連接,點擊監視tab,可以看到如下界面了.
在上圖中可以看到cpu利用率和垃圾回收活動,然后是堆棧使用情況(這兩個在分析tomcat性能時很重要)。下面是類的使用情況,最后一個是線程活動情況。
點擊線程tab可以看到:下圖可以非常清晰的看到線程活動情況,那些線程正在執行,哪些線程正在等待中,以及執行完畢的線程等。這里可以看到每個線程的狀態,點擊某個線程右鍵可以查看該線程的詳細情況:
線程死鎖檢測
前面已經介紹如何安裝插件,jvisualvm推薦16種插件,其中Threads Inspector插件可以輕松地檢測到死鎖,當壓測過程中出現死鎖,在線程tab頁會出現如下的提示
從上圖可知,檢測到死鎖,可以通過【線程Dump】查看具體的死鎖線程的信息
Main.Java 21行:Thread-0當前鎖定了
<0x00000000dbb308f0>,並且在等待鎖定<0x00000000dbb308c0>
Main.Java 32行:Thread-1當前鎖定了
<0x00000000dbb308c0> ,並且在等待鎖定
<0x00000000dbb308f0>
兩個進程互相都需要對方線程釋放其所占有資源(java.lang.String),而根據【請求與保持條件】原則,顯然不會釋放已經到手的資源,可以說是吃着碗里的,看着鍋里的,兩邊都想占有。
內存分析
jvisualvm 通過檢測 JVM 中加載的類和對象信息等幫助分析內存使用情況,可以通過 jvisualvm的監視標簽和 抽樣器對應用程序進行內存分析。下面以分析內存泄漏為例。
性能測試過程中,如何去發現內存問題:jvm分配的內存是否夠用,是否有大對象占用太多內存,監控時哪樣的圖形反映內存泄漏的風險?
如上圖中,堆大小和使用的堆大小一目了然。當壓力穩定的情況下,出現如上的圖形,說明有內存泄漏的風險。從上圖可以看到,雖然每次Full GC,JVM內存會有部分回收,但回收並不徹底,不可回收的內存對象會越來越多,這樣便會出現以上的一個步步高的趨勢。在Full GC無法回收的對象越來越多時,最終已使用內存達到系統分配的內存最大值,系統最后無內存可分配,最終內存溢出,導致down機。Java盡管采用自動的內存管理方式,但是仍然存在泄露的可能,JVM認為對象沒有引用時,會把這個對象視為垃圾。Java內存泄露就存在於,這個對象實際上已經不再需要,但是仍然存在引用,此時就會產生內存泄露。
如何找到導致內存泄漏的程序?打開抽樣器標簽:點擊內存后如下圖:
多次執行gc前后分別堆dump一次,進入最后dump出來的堆標簽,點擊類:點擊右上角:“與另一個堆存儲對比”。如圖選擇第一次導出的dump內容比較:
比較結果如下:
dump出來的類中看到有很多是jdk的基礎類型,那么分析程序的時候顯然只分析和程序員相關的,按實例數或大小排序優先分析,找到我們程序自定義的包名,如上圖中com.memory.test。可以看到多次gc后,TestMemory對象實例一直在增加,說明該對象引用的方法可能存在內存泄漏。如何查看對象引用關系呢?雙擊選擇類TestMemory,如下所示:
左側是創建的實例總數,右側上部為該實例的結構,下面為引用說明,從圖中可以看出在類CyclicDependencies里面被引用了,並且被HashMap引用。
如此可以確定泄漏的位置,進而根據實際情況進行分析解決。
cpu分析
jvisualvm 能夠監控應用程序在一段時間的 CPU 的使用情況,顯示 CPU 的使用率、方法的執行效率和頻率等相關數據幫助發現應用程序的性能瓶頸。可以通過 jvisualvm的監視標簽和 抽樣器對應用程序進行 CPU 性能分析。
在監視標簽內,可以查看 CPU 的使用率以及垃圾回收活動對性能的影響。過高的 CPU 使用率可能是由於項目中存在低效的代碼,可以通過 抽樣器對 CPU抽樣進行詳細的分析。如果垃圾回收活動過於頻繁,占用了較高的 CPU 資源,可能是由內存不足或者是新生代和舊生代分配不合理導致的等。如下圖中:
觀察右邊的使用堆圖形,圖形呈鋸齒狀,說明垃圾不斷地被回收,cpu圖形中也發現頻繁的gc導致cpu不穩定,再通過監控gc情況進一步分析是否內存分配不合理,使用的是什么垃圾回收策,是否有回收不掉的對象。關於gc需要理解java內存模型、垃圾回收機制,這里不再詳述。
項目實例分析
以近期前置機接口性能測試為例,圍繞如何根據性能指標通過監控jvm分析系統性能瓶頸。下面是投標接口壓測過程中表現出來的前端指標
上圖中圖1為並發用戶數,2圖為點擊率,圖3為tps。並發用戶數與點擊率,與tpsf都是正相關的關系。即當並發用戶數不斷增加時,點擊率也應線性地增加,如果用戶數增加點擊率增加長緩慢或平穩或下降,都是性能不良好的表現。圖中當並發用戶加載到40個左右時,點擊率都是跟隨着性能增漲,用戶數再往上增加時,點擊率不增漲反而有所下降。說明服務器處理能力遇到瓶頸。這時通過jvisualvm工具監控到cpu不足,jvm內存頻繁的回收,如下圖
結合打印gc信息,發現ygc每秒1次
優化的思路從減少ygc入手,可通過調整gc算法,增大年輕代大小來優化。gc是一個復雜的過程,調整參數后需要經過詳細的測試,驗證是否達到優化的目的。本人水平也非常有限,講得不對的地方,還望大家指教一起探討。
cpu方面結合top,可發現cpu load過高,如下圖:
再進行cpu抽樣,可查看哪些方法占用cpu高,那么優化cpu就可從兩方面入手:1、硬件上增加cpu 2、代碼上對占用cpu的高方法進行代碼分析優化,當然這個得開發人員進行配合分析了。如下圖中,打印日志的方法消耗cpu比較高,可把日志級別調低。
增加cpu后,性能指標得到明顯提升,最高tps可達到190。如下圖