本文介紹本人的一次使用Windbg分析dump文件找出死鎖的過程,並重點介紹如何肯定線程所等待的鎖及判斷是否出現了死鎖。html
對於如何安裝及設置Windbg請參考:《使用Windbg和SoS擴展調試分析.NET程序》http://www.cnblogs.com/shanyou/archive/2006/12/23/601004.html測試
今天,部署到生產環境中的軟件再次發生了不響應請求的問題,看了系統日誌與軟件自己的log都沒發現異常,而在任務管理器中軟件佔用了1G多的內存,有點偏高(正常是300M左右)。因爲本人不在現場,只能經過遠程的方式查看,同時故障出現間隔比較長(將近一週),在生產環境中也就沒法使用VS進行調試。線程
無心中在資源監視器的CPU頁看到軟件的線程數是1.7萬個,內存頁的提交內存使用也將近18G,同時線程數與提交內存也在緩慢增長。當時就想是否是因爲某種緣由致使線程沒法退出從而在線程數太多的時候導致軟件不響應請求(後來的調試也證明是死鎖致使的)。3d
因爲故障難以重現(只在生產環境中長時間運行纔會出現,在測試環境沒法出現),只能對正在運行的軟件進行分析。調試
這時候就請出了大名鼎鼎的Windbg,下面是詳細的過程。日誌
抓取dump的方法,能夠參考《抓取user mode dump文件的幾重境界》http://www.cnblogs.com/pugang/archive/2013/02/18/2916211.htmlhtm
我選擇的是使用圖形化操做的方式,在任務管理器的進程頁中,右鍵須要抓取的程序,選擇「建立轉儲文件」對象
運行完成後將會彈出成功對話框並提示dump文件的所在blog
運行Windbg,在File菜單下選擇Open Crash Dump,選擇上面抓取的dump文件進程
在Windbg下側的命令輸入框中輸入「.load C:\Windows\Microsoft.NET\Framework64\v4.0.30319\sos」並回車加載SOS.dll。因爲我是調試net4.0 64位的軟件,因此使用了Framework64\v4.0.30319下的sos,其它版本請選擇對應位置的sos進行加載
使用「!threads」命令列出全部的線程,發現一共存在17306個線程
使用「~17306s」命令切換到最後一個線程,並使用「!clrstack」命令輸出當前線程的調用堆棧,發現存在「System.Threading.Monitor.Enter(System.Object)」,代表線程正在請求一個鎖。因爲得不到鎖,所以線程卡死
切換到其它線程查看調用堆棧,都是由於一樣的緣由致使線程卡死,這時候能夠初步判斷這些線程是由於死鎖致使執行不下去
使用「!syncblk」命令列出全部正在使用的鎖,其中MonitorHeld與Recursion列表示了請求鎖的線程數量狀況,Info列表示哪一個線程擁有了鎖,SyncBlock列表示鎖對象的地址。如MonitorHeld與Recursion的值爲3775與1那行表示第40個線程擁有了這個鎖,其它(3775-1)/2=1887個線程在等待鎖,鎖對象地址爲0000000003c812f0。看到如此多的線程在請求同一個鎖,就知道狀況不正常,看來離死鎖的真相又近了一步
接下來的過程就是:找到某個線程(如線程A)請求的鎖(如鎖J),查看哪一個線程(如線程B)擁有這個鎖(鎖J)及這個線程請求的鎖(鎖K),接着查看哪一個線程(如線程C)擁有這個鎖(鎖K)及這個線程請求的鎖(鎖L),重複查看的過程,看最終是否有一個線程(如線程D)請求前面出現的任意一個鎖(如線程B擁有的鎖J),造成環狀,這時便可判斷其爲死鎖
這裏從線程17306開始分析,使用「!clrstack -l」命令列出當前線程的調用堆棧及其使用的局部變量
在調用「System.Threading.Monitor.Enter(System.Object)」以前的一個方法內,應該存在做爲局部變量的線程請求的鎖對象
這裏猜想下面的0000000003c812f0就是這個鎖對象,經過查找上面的鎖列表,肯定了這個猜想,同時知道線程40擁有這個鎖
使用「~40s」命令切換到線程40,並使用「!clrstack -l」命令列出當前線程的調用堆棧及其使用的局部變量,經過查找鎖列表肯定000000000317ac10爲當前線程請求的鎖對象,同時知道線程26擁有這個鎖
一樣使用「~26s」與「!clrstack -l」命令找到線程26請求的鎖對象00000000044a81a8,這個鎖對象被線程43擁有
接着使用「~43s」與「!clrstack -l」命令找到線程43請求的鎖對象000000000317ac10,這個鎖對象被線程26擁有
此時能夠發現線程26與線程43之間造成了死鎖
終於真相大白了,上面的過程成功找到了死鎖
也由此推斷因爲死鎖的存在,致使後面新建的線程因爲得不到請求的鎖,一直不能執行下去,更不可能釋放所佔用的內存,從而使得線程數與內存佔用在一直升高,直到軟件沒法響應請求爲止
接下來的工做就是查看死鎖線程的調用堆棧,結合軟件源代碼分析死鎖造成時軟件的運行狀況,並更改處理邏輯以免死鎖的產生