原創(chuàng)|使用教程|編輯:鄭恭琳|2020-07-13 14:39:57.827|閱讀 193 次
概述:在本文中,學(xué)習(xí)如何監(jiān)控Java線程以了解應(yīng)用程序中引起性能問題的特定代碼行。
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
相關(guān)鏈接:
在本文中,學(xué)習(xí)如何監(jiān)控Java線程以了解應(yīng)用程序中引起性能問題的特定代碼行。
與線程相關(guān)的問題可能會以某種方式對Web API應(yīng)用程序的性能產(chǎn)生不利影響,這些方式通常難以診斷且難以解決。清晰了解線程的行為對于實(shí)現(xiàn)最佳性能至關(guān)重要。在本文中,我將向您展示如何使用Parasoft SOAtest的負(fù)載測試JVM線程監(jiān)視器來查看JVM的線程活動,以及動態(tài)統(tǒng)計圖和可配置的線程轉(zhuǎn)儲圖,這些圖可以指向造成性能損失的代碼行。線程使用效率低下。使用Parasoft SOAtest的負(fù)載測試模塊,您可以將任何功能測試轉(zhuǎn)換為負(fù)載和性能測試。
我們將跟隨一個假想的Java開發(fā)團(tuán)隊(duì),在創(chuàng)建Web API應(yīng)用程序時遇到一些常見的線程問題,并診斷一些與線程相關(guān)的常見性能問題。之后,我們將看一下實(shí)際應(yīng)用程序的更復(fù)雜的示例。(請注意,以下示例中的一些次優(yōu)代碼是出于演示目的而有意添加的。)
我們假設(shè)的Java開發(fā)團(tuán)隊(duì)著手進(jìn)行一個新項(xiàng)目——REST API銀行應(yīng)用程序。該團(tuán)隊(duì)建立了一個持續(xù)集成(CI)基礎(chǔ)結(jié)構(gòu)來支持新項(xiàng)目,其中包括使用Parasoft SOAtest的負(fù)載測試模塊進(jìn)行的定期CI工作,以連續(xù)測試新應(yīng)用程序的性能。(有關(guān)如何設(shè)置自動化性能測試的更多詳細(xì)信息,請參閱我以前的文章《DevOps交付管道中的負(fù)載和性能測試》。)
銀行應(yīng)用程序版本1:初始實(shí)施中的競爭條件
Bank應(yīng)用程序代碼開始增長,并且測試正在運(yùn)行。但是,團(tuán)隊(duì)注意到,在實(shí)施新的轉(zhuǎn)帳操作之后,銀行應(yīng)用程序開始在較高的負(fù)載下出現(xiàn)零星的故障。該失敗來自帳戶驗(yàn)證方法,該方法有時會在透支保護(hù)帳戶中發(fā)現(xiàn)負(fù)余額。帳戶驗(yàn)證失敗會導(dǎo)致異常和API的HTTP 500響應(yīng)。開發(fā)人員懷疑這可能是由處理同一帳戶上的并發(fā)轉(zhuǎn)移操作的不同線程調(diào)用的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: }
銀行應(yīng)用程序版本2:添加同步
開發(fā)人員決定在轉(zhuǎn)帳操作中同步對帳戶的訪問,以防止出現(xiàn)可疑的比賽情況:
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: }
該團(tuán)隊(duì)還將JVM線程監(jiān)視器添加到針對REST API應(yīng)用程序運(yùn)行的負(fù)載測試項(xiàng)目。該監(jiān)視器將提供死鎖、阻塞、駐留和總線程的圖表,并將記錄這些狀態(tài)下的線程轉(zhuǎn)儲。
代碼更改被推送到存儲庫,并由CI性能測試過程獲取。第二天,開發(fā)人員發(fā)現(xiàn)性能測試在一夜之間失敗了。開始進(jìn)行轉(zhuǎn)帳操作性能測試后不久,Bank應(yīng)用程序停止響應(yīng)。快速查看“負(fù)載測試”報告中的“JVM線程監(jiān)視器”圖,可以發(fā)現(xiàn)Bank應(yīng)用程序中存在死鎖線程(請參見圖1.a)。死鎖詳細(xì)信息由JVM線程監(jiān)視器保存為報告的一部分,并顯示了導(dǎo)致死鎖的確切代碼行(請參見清單1.b)。
圖1.a-被測應(yīng)用程序(AUT)中死鎖的線程數(shù)。
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線程監(jiān)視器保存的死鎖詳細(xì)信息
銀行應(yīng)用程序版本3:解決僵局
銀行應(yīng)用程序開發(fā)人員決定通過在單個全局對象上進(jìn)行同步來解決死鎖,并修改傳輸方法代碼,如下所示:
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的競爭狀況,但是平均傳輸操作響應(yīng)時間從30毫秒增加到150毫秒以上,增加了五倍以上(見圖2.a)。JVM線程監(jiān)視器的BlockedRatio圖形顯示,在負(fù)載測試執(zhí)行期間,有60%到75%的應(yīng)用程序線程處于BLOCKED狀態(tài)(請參見圖2.b)。監(jiān)視器保存的詳細(xì)信息表明,嘗試進(jìn)入第15行的全局同步部分時,應(yīng)用程序線程被阻止(請參見清單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線程監(jiān)視器保存的阻塞線程詳細(xì)信息
銀行應(yīng)用程序版本4:提高同步性能
開發(fā)團(tuán)隊(duì)尋找一種解決方案,該解決方案可以解決競態(tài)條件而又不會引入死鎖和損害應(yīng)用程序的響應(yīng)能力,并且經(jīng)過一些研究找到了使用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(優(yōu)化鎖定)的銀行應(yīng)用程序轉(zhuǎn)移操作的響應(yīng)時間,在藍(lán)色圖形中顯示了版本3(全局對象同步)的響應(yīng)時間,在綠色圖形中顯示了版本1(非同步轉(zhuǎn)移操作)的響應(yīng)時間。這些圖表明,由于鎖定優(yōu)化,轉(zhuǎn)移操作性能得到了顯著改善。同步(紅色圖)和非同步(綠色圖)傳輸操作之間的細(xì)微差別是防止競爭條件的可接受價格。
圖3.a——銀行應(yīng)用程序版本4(紅色)、版本3(藍(lán)色)和版本1(綠色)的傳輸操作響應(yīng)時間。
示例1:增加應(yīng)用程序響應(yīng)時間
上面的“銀行應(yīng)用程序”示例旨在說明如何解決由線程問題導(dǎo)致的性能下降的典型隔離情況。實(shí)際情況可能更復(fù)雜——圖4中的圖形顯示了一個生產(chǎn)REST API應(yīng)用程序的示例,該應(yīng)用程序的響應(yīng)時間隨著性能測試的進(jìn)行而不斷增長。在測試的上半部分,應(yīng)用程序響應(yīng)時間以較低的速率增長,在下半部分中以較高的速率增長(見圖4.a)。在測試的前半部分,響應(yīng)時間的增長與應(yīng)用程序線程在“阻塞”狀態(tài)下花費(fèi)的總時間相關(guān)(請參見圖4.b)。在測試的后半部分,響應(yīng)時間的增長與處于PARKED狀態(tài)的應(yīng)用程序線程數(shù)相關(guān)。負(fù)載測試JVM線程監(jiān)視器捕獲的堆棧跟蹤提供了詳細(xì)信息:一個指向同步塊,該塊導(dǎo)致在BLOCKED狀態(tài)下花費(fèi)過多時間。另一個指出使用java.util.concurrent.locks類進(jìn)行同步的代碼行,該類負(fù)責(zé)使線程保持在PARKED狀態(tài)。在優(yōu)化了這些代碼區(qū)域之后,兩個性能問題都得到解決。
示例2:應(yīng)用程序響應(yīng)時間中的偶發(fā)事件
負(fù)載測試JVM線程監(jiān)視器可以非常有用地捕獲與線程相關(guān)的罕見問題的詳細(xì)信息,尤其是在性能測試是自動執(zhí)行且定期運(yùn)行的情況下。圖5中的圖形顯示了生產(chǎn)REST API應(yīng)用程序,該應(yīng)用程序在平均和最大響應(yīng)時間上都有間歇性的峰值(見圖5.a)。
應(yīng)用程序響應(yīng)時間的這種峰值通常可能是由于JVM垃圾收集器配置欠佳所致,但是在這種情況下,BlockedTime監(jiān)視器中的相關(guān)峰值(請參見圖5.b)指出線程同步是問題的根源。BlockedThreads監(jiān)視器通過捕獲阻塞線程和阻塞線程的堆棧跟蹤,在這里提供了更多幫助。重要的是要了解BlockedTime和BlockedThreads監(jiān)視器之間的區(qū)別。
BlockedTime監(jiān)視程序顯示JVM線程在監(jiān)視程序調(diào)用之間處于BLOCKED狀態(tài)所花費(fèi)的累積時間,而BlockedThreads監(jiān)視程序則對JVM線程進(jìn)行定期快照,并在這些快照中搜索被阻止的線程。因此,BlockedTime監(jiān)視器在檢測線程阻塞方面更為可靠,但它只是警告您存在線程阻塞問題。BlockedThreads監(jiān)視器因?yàn)樗@取常規(guī)線程快照而可能會丟失某些線程阻塞事件,但從正面來看,當(dāng)它捕獲此類事件時,它會提供導(dǎo)致阻塞的詳細(xì)信息。因此,BlockedThreads監(jiān)視器是否將捕獲與代碼相關(guān)的阻塞狀態(tài)的詳細(xì)信息是一個統(tǒng)計問題,但是如果定期進(jìn)行性能測試,您很快就會在BlockedThreads圖中看到峰值(參見圖5.c),這意味著已捕獲阻塞和阻塞線程的詳細(xì)信息。這些詳細(xì)信息將使您指向?qū)е聭?yīng)用程序響應(yīng)時間極少出現(xiàn)尖峰的代碼行。
創(chuàng)建性能回歸控件
負(fù)載測試JVM線程監(jiān)視器除了是一種有效的診斷工具外,還可以用于創(chuàng)建與線程相關(guān)的問題的性能回歸控件。發(fā)現(xiàn)并解決了此類性能問題后,請為其創(chuàng)建性能回歸測試。該測試將包括現(xiàn)有或新的性能測試運(yùn)行以及新的回歸控件。對于Parasoft負(fù)載測試,這將是相關(guān)JVM線程監(jiān)控器通道的QoS監(jiān)控器指標(biāo)。例如,對于示例1(圖4)中描述的問題,創(chuàng)建一個負(fù)載測試QoS監(jiān)視器度量標(biāo)準(zhǔn),該度量標(biāo)準(zhǔn)檢查應(yīng)用程序線程處于“阻塞”狀態(tài)所花費(fèi)的時間,以及另一個度量標(biāo)準(zhǔn),用于檢查處于“已駐留”狀態(tài)的線程數(shù)。在Java應(yīng)用程序中創(chuàng)建命名線程始終是一個好主意,這將使您可以將性能回歸控件應(yīng)用于經(jīng)過名稱過濾的一組線程。
下表總結(jié)了哪些線程監(jiān)視器通道以及何時使用:
線程監(jiān)控器通道 |
何時使用 |
死鎖線程
MonitorDeadlockedThreads
|
一般來說,死鎖可以說是與線程相關(guān)的最嚴(yán)重的問題,它可能會完全破壞應(yīng)用程序的功能。 |
阻塞線程 封鎖時間
封鎖比率
BlockedCount |
常規(guī)情況中,在“阻塞”狀態(tài)下花費(fèi)的時間過多或“阻塞”線程數(shù)通常會導(dǎo)致性能下降。監(jiān)視這些參數(shù)中的至少一個。也可用于性能回歸控制。 |
停放線程 |
一般來說,處于PARKED狀態(tài)的線程數(shù)量過多可能表明java.util.concurrent.locks類的使用不正確以及其他線程問題。也可用于性能回歸控制。 |
總線程 |
常規(guī)情況中,用于將處于阻塞,駐留或其他狀態(tài)的線程數(shù)與線程總數(shù)進(jìn)行比較。 也可用于性能回歸控制。 |
睡眠線程 等待線程 等待時間 等待比率 等待計數(shù) |
偶爾,用于與這些狀態(tài)相關(guān)的性能回歸控制和探索性測試。 |
新線程 未知線程 |
很少,用于與這些線程狀態(tài)相關(guān)的性能回歸控件。 |
Parasoft的JVM Threads Monitor是一種有效的診斷工具,可檢測與線程相關(guān)的JVM性能問題并創(chuàng)建高級性能回歸控件。與SOAtest的負(fù)載測試連續(xù)體結(jié)合使用時,JVM線程監(jiān)視器通過記錄相關(guān)的線程詳細(xì)信息(這些代碼行導(dǎo)致性能不佳)來幫助消除重現(xiàn)性能問題的步驟,并幫助您提高應(yīng)用程序性能以及開發(fā)人員和質(zhì)量保證生產(chǎn)率。
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請郵件反饋至chenjj@fc6vip.cn