在定位 .NET 應(yīng)用程序中的高 CPU 占用問題時,WinDbg 是非常強(qiáng)大的工具之一,尤其配合 SOS 擴(kuò)展使用可以快速鎖定“忙線程”或死鎖等問題。
本文將基于一次實際的分析流程,演示如何一步步定位由線程鎖引起的 CPU 高占用。
1. 加載 SOS 擴(kuò)展(針對 .NET)
首先,我們需要加載 SOS.dll。根據(jù)你所調(diào)試的 .NET 版本不同,使用 .loadby 指令時的模塊名也不同:
.loadby sos clr
注意:
-
.NET Framework使用的是clr.dll,所以.loadby sos clr正確; -
如果你調(diào)試的是
.NET Core或.NET 5+,對應(yīng)模塊可能是coreclr.dll; -
可使用
lm命令確認(rèn)實際加載的模塊名。
2.查看cpu占用高的線程
!runaway
這個命令顯示自 WinDbg 附加后各線程的 CPU 占用時間。
3. 查看每個線程的調(diào)用棧
查看所有線程的調(diào)用棧是分析的關(guān)鍵一步。我們使用以下命令:
~* k
這會列出所有線程的 原生調(diào)用堆棧(native stack)。
關(guān)注以下三類線程特征:
持續(xù)執(zhí)行的線程(高 CPU 嫌疑線程)
棧頂函數(shù)是業(yè)務(wù)邏輯方法、算法處理、循環(huán)等,說明該線程在“忙”,是最需要關(guān)注的對象。
卡在等待(阻塞)狀態(tài)的線程
以下函數(shù)說明線程被阻塞,可能在等待鎖或資源:
-
WaitForSingleObject -
Monitor.Enter -
WaitOne -
Sleep
找到等待的資源后,看正在等待什么,如果正在等待GC,則繼續(xù)找誰在GC

找到在執(zhí)行 GC 的線程
如果調(diào)用棧中包含以下函數(shù),說明線程正在 GC 中:
-
clr!GCHeap::GarbageCollect -
clr!SVR::gc_heap::gc1 -
clr!SVR::gc_heap::gc2 -
clr!SVR::gc_heap::gc3 -
clr!GCHeap::GarbageCollectGeneration -
clr!SVR::GCHeap::GarbageCollect -
clr!GCHeap::gc_thread_function -
GCInterface::Collect
頻繁GC會掛起線程,增加CPU消耗。

4. 分析具體線程
在上一步中,如果你發(fā)現(xiàn)某個線程(例如線程 28)調(diào)用棧活躍、函數(shù)棧持續(xù)變化,或者涉及 GC、鎖等待,可以使用以下命令聚焦:
~28s
!clrstack
這將切換到線程 28 并顯示它的托管調(diào)用棧,便于你進(jìn)一步確認(rèn)是否存在如下情況:
-
死循環(huán)或密集計算導(dǎo)致高 CPU;
-
一直等待某個鎖對象,導(dǎo)致其他線程堆積;
-
某些資源釋放不及時,導(dǎo)致線程頻繁爭搶。
總結(jié)
通過上述方法,我們可以初步判斷線程是否因鎖或其他因素導(dǎo)致 CPU 占用異常。在實際排查中,掌握如下三點尤為重要:
-
先宏觀查看所有線程調(diào)用棧;
-
識別忙線程 / 等待線程/ GC線程 ;
-
進(jìn)一步使用
!clrstack分析托管調(diào)用棧。
這是一種穩(wěn)定、高效的診斷思路,尤其適用于高 CPU 的 dump 分析場景。
浙公網(wǎng)安備 33010602011771號