原創|使用教程|編輯:鄭恭琳|2020-07-13 14:39:57.827|閱讀 193 次
概述:在本文中,學習如何監控Java線程以了解應用程序中引起性能問題的特定代碼行。
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
相關鏈接:
在本文中,學習如何監控Java線程以了解應用程序中引起性能問題的特定代碼行。
與線程相關的問題可能會以某種方式對Web API應用程序的性能產生不利影響,這些方式通常難以診斷且難以解決。清晰了解線程的行為對于實現最佳性能至關重要。在本文中,我將向您展示如何使用Parasoft SOAtest的負載測試JVM線程監視器來查看JVM的線程活動,以及動態統計圖和可配置的線程轉儲圖,這些圖可以指向造成性能損失的代碼行。線程使用效率低下。使用Parasoft SOAtest的負載測試模塊,您可以將任何功能測試轉換為負載和性能測試。
我們將跟隨一個假想的Java開發團隊,在創建Web API應用程序時遇到一些常見的線程問題,并診斷一些與線程相關的常見性能問題。之后,我們將看一下實際應用程序的更復雜的示例。(請注意,以下示例中的一些次優代碼是出于演示目的而有意添加的。)
我們假設的Java開發團隊著手進行一個新項目——REST API銀行應用程序。該團隊建立了一個持續集成(CI)基礎結構來支持新項目,其中包括使用Parasoft SOAtest的負載測試模塊進行的定期CI工作,以連續測試新應用程序的性能。(有關如何設置自動化性能測試的更多詳細信息,請參閱我以前的文章《DevOps交付管道中的負載和性能測試》。)
銀行應用程序版本1:初始實施中的競爭條件
Bank應用程序代碼開始增長,并且測試正在運行。但是,團隊注意到,在實施新的轉帳操作之后,銀行應用程序開始在較高的負載下出現零星的故障。該失敗來自帳戶驗證方法,該方法有時會在透支保護帳戶中發現負余額。帳戶驗證失敗會導致異常和API的HTTP 500響應。開發人員懷疑這可能是由處理同一帳戶上的并發轉移操作的不同線程調用的IAccount.withdraw方法中的競爭條件引起的:
13: public boolean transfer(IAccount from, IAccount to, int amount) { 14: if (from.withdraw(amount)) { 15: to.deposit(amount); 16: return true; 17: } 18: return false; 19: }
銀行應用程序版本2:添加同步
開發人員決定在轉帳操作中同步對帳戶的訪問,以防止出現可疑的比賽情況:
14: public boolean transfer(IAccount from, IAccount to, int amount) { 15: synchronized (to) { 16: synchronized (from) { 17: if (from.withdraw(amount)) { 18: to.deposit(amount); 19: return true; 20: } 21: } 22: } 23: return false; 24: }
該團隊還將JVM線程監視器添加到針對REST API應用程序運行的負載測試項目。該監視器將提供死鎖、阻塞、駐留和總線程的圖表,并將記錄這些狀態下的線程轉儲。
代碼更改被推送到存儲庫,并由CI性能測試過程獲取。第二天,開發人員發現性能測試在一夜之間失敗了。開始進行轉帳操作性能測試后不久,Bank應用程序停止響應。快速查看“負載測試”報告中的“JVM線程監視器”圖,可以發現Bank應用程序中存在死鎖線程(請參見圖1.a)。死鎖詳細信息由JVM線程監視器保存為報告的一部分,并顯示了導致死鎖的確切代碼行(請參見清單1.b)。
圖1.a-被測應用程序(AUT)中死鎖的線程數。
DEADLOCKED thread: http-nio-8080-exec-20 com.parasoft.demo.bank.v2.ATM_2.transfer:15 com.parasoft.demo.bank.ATM.transfer:21 ... Blocked by: DEADLOCKED thread: http-nio-8080-exec-7 com.parasoft.demo.bank.v2.ATM_2.transfer:16 com.parasoft.demo.bank.ATM.transfer:21 com.parasoft.demo.bank.v2.RestController_2.transfer:29 sun.reflect.GeneratedMethodAccessor58.invoke:-1 sun.reflect.DelegatingMethodAccessorImpl.invoke:-1 java.lang.reflect.Method.invoke:-1 org.springframework.web.method.support.InvocableHandlerMethod.doInvoke:209
清單1.b——JVM線程監視器保存的死鎖詳細信息
銀行應用程序版本3:解決僵局
銀行應用程序開發人員決定通過在單個全局對象上進行同步來解決死鎖,并修改傳輸方法代碼,如下所示:
14: public boolean transfer(IAccount from, IAccount to, int amount) { 15: synchronized (Account.class) { 17: if (from.withdraw(amount)) { 18: to.deposit(amount); 19: return true; 20: } 21: } 22: return false; 23: }
該更改解決了版本2的死鎖問題和版本1的競爭狀況,但是平均傳輸操作響應時間從30毫秒增加到150毫秒以上,增加了五倍以上(見圖2.a)。JVM線程監視器的BlockedRatio圖形顯示,在負載測試執行期間,有60%到75%的應用程序線程處于BLOCKED狀態(請參見圖2.b)。監視器保存的詳細信息表明,嘗試進入第15行的全局同步部分時,應用程序線程被阻止(請參見清單2.c)。
解決銀行申請僵局
BLOCKED thread: http-nio-8080-exec-4 com.parasoft.demo.bank.v3.ATM_3.transfer:15 com.parasoft.demo.bank.ATM.transfer:21 com.parasoft.demo.bank.v3.RestController_3.transfer:29 ... Blocked by: SLEEPING thread: http-nio-8080-exec-8 java.lang.Thread.sleep:-2 com.parasoft.demo.bank.Account.doWithdraw:64 com.parasoft.demo.bank.Account.withdraw:31
清單2.c——JVM線程監視器保存的阻塞線程詳細信息
銀行應用程序版本4:提高同步性能
開發團隊尋找一種解決方案,該解決方案可以解決競態條件而又不會引入死鎖和損害應用程序的響應能力,并且經過一些研究找到了使用java.util.concurrent.locks.ReentrantLock類的有前途的解決方案:
19: private boolean doTransfer(Account from, Account to, int amount) { 20: try { 21: acquireLocks(from.getReentrantLock(), to.getReentrantLock()); 22: if (from.withdraw(amount)) { 23: to.deposit(amount); 24: return true; 25: } 26: return false; 27: } finally { 28: releaseLocks(from.getReentrantLock(), to.getReentrantLock()); 29: } 30:
圖3a中的圖形在紅色圖形中顯示了版本4(優化鎖定)的銀行應用程序轉移操作的響應時間,在藍色圖形中顯示了版本3(全局對象同步)的響應時間,在綠色圖形中顯示了版本1(非同步轉移操作)的響應時間。這些圖表明,由于鎖定優化,轉移操作性能得到了顯著改善。同步(紅色圖)和非同步(綠色圖)傳輸操作之間的細微差別是防止競爭條件的可接受價格。
圖3.a——銀行應用程序版本4(紅色)、版本3(藍色)和版本1(綠色)的傳輸操作響應時間。
示例1:增加應用程序響應時間
上面的“銀行應用程序”示例旨在說明如何解決由線程問題導致的性能下降的典型隔離情況。實際情況可能更復雜——圖4中的圖形顯示了一個生產REST API應用程序的示例,該應用程序的響應時間隨著性能測試的進行而不斷增長。在測試的上半部分,應用程序響應時間以較低的速率增長,在下半部分中以較高的速率增長(見圖4.a)。在測試的前半部分,響應時間的增長與應用程序線程在“阻塞”狀態下花費的總時間相關(請參見圖4.b)。在測試的后半部分,響應時間的增長與處于PARKED狀態的應用程序線程數相關。負載測試JVM線程監視器捕獲的堆棧跟蹤提供了詳細信息:一個指向同步塊,該塊導致在BLOCKED狀態下花費過多時間。另一個指出使用java.util.concurrent.locks類進行同步的代碼行,該類負責使線程保持在PARKED狀態。在優化了這些代碼區域之后,兩個性能問題都得到解決。
示例2:應用程序響應時間中的偶發事件
負載測試JVM線程監視器可以非常有用地捕獲與線程相關的罕見問題的詳細信息,尤其是在性能測試是自動執行且定期運行的情況下。圖5中的圖形顯示了生產REST API應用程序,該應用程序在平均和最大響應時間上都有間歇性的峰值(見圖5.a)。
應用程序響應時間的這種峰值通常可能是由于JVM垃圾收集器配置欠佳所致,但是在這種情況下,BlockedTime監視器中的相關峰值(請參見圖5.b)指出線程同步是問題的根源。BlockedThreads監視器通過捕獲阻塞線程和阻塞線程的堆棧跟蹤,在這里提供了更多幫助。重要的是要了解BlockedTime和BlockedThreads監視器之間的區別。
BlockedTime監視程序顯示JVM線程在監視程序調用之間處于BLOCKED狀態所花費的累積時間,而BlockedThreads監視程序則對JVM線程進行定期快照,并在這些快照中搜索被阻止的線程。因此,BlockedTime監視器在檢測線程阻塞方面更為可靠,但它只是警告您存在線程阻塞問題。BlockedThreads監視器因為它獲取常規線程快照而可能會丟失某些線程阻塞事件,但從正面來看,當它捕獲此類事件時,它會提供導致阻塞的詳細信息。因此,BlockedThreads監視器是否將捕獲與代碼相關的阻塞狀態的詳細信息是一個統計問題,但是如果定期進行性能測試,您很快就會在BlockedThreads圖中看到峰值(參見圖5.c),這意味著已捕獲阻塞和阻塞線程的詳細信息。這些詳細信息將使您指向導致應用程序響應時間極少出現尖峰的代碼行。
創建性能回歸控件
負載測試JVM線程監視器除了是一種有效的診斷工具外,還可以用于創建與線程相關的問題的性能回歸控件。發現并解決了此類性能問題后,請為其創建性能回歸測試。該測試將包括現有或新的性能測試運行以及新的回歸控件。對于Parasoft負載測試,這將是相關JVM線程監控器通道的QoS監控器指標。例如,對于示例1(圖4)中描述的問題,創建一個負載測試QoS監視器度量標準,該度量標準檢查應用程序線程處于“阻塞”狀態所花費的時間,以及另一個度量標準,用于檢查處于“已駐留”狀態的線程數。在Java應用程序中創建命名線程始終是一個好主意,這將使您可以將性能回歸控件應用于經過名稱過濾的一組線程。
下表總結了哪些線程監視器通道以及何時使用:
線程監控器通道 |
何時使用 |
死鎖線程
MonitorDeadlockedThreads
|
一般來說,死鎖可以說是與線程相關的最嚴重的問題,它可能會完全破壞應用程序的功能。 |
阻塞線程 封鎖時間
封鎖比率
BlockedCount |
常規情況中,在“阻塞”狀態下花費的時間過多或“阻塞”線程數通常會導致性能下降。監視這些參數中的至少一個。也可用于性能回歸控制。 |
停放線程 |
一般來說,處于PARKED狀態的線程數量過多可能表明java.util.concurrent.locks類的使用不正確以及其他線程問題。也可用于性能回歸控制。 |
總線程 |
常規情況中,用于將處于阻塞,駐留或其他狀態的線程數與線程總數進行比較。 也可用于性能回歸控制。 |
睡眠線程 等待線程 等待時間 等待比率 等待計數 |
偶爾,用于與這些狀態相關的性能回歸控制和探索性測試。 |
新線程 未知線程 |
很少,用于與這些線程狀態相關的性能回歸控件。 |
Parasoft的JVM Threads Monitor是一種有效的診斷工具,可檢測與線程相關的JVM性能問題并創建高級性能回歸控件。與SOAtest的負載測試連續體結合使用時,JVM線程監視器通過記錄相關的線程詳細信息(這些代碼行導致性能不佳)來幫助消除重現性能問題的步驟,并幫助您提高應用程序性能以及開發人員和質量保證生產率。
本站文章除注明轉載外,均為本站原創或翻譯。歡迎任何形式的轉載,但請務必注明出處、不得修改原文相關鏈接,如果存在內容上的異議請郵件反饋至chenjj@fc6vip.cn