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