昨天服務器的CPU突然100%,此服務已經運行幾年了,都平安無事。既然問題出現當然要找出這個遺留多年的小概率問題。出現cpu 100% 一般就是哪里出現了無法跳出的死循環。
1、獲取進程的內存信息
服務器使用的window server 直接右鍵創建轉儲文件即可。這個直接點點的方式是使用window server最方便的地方(^_^)。
2、加載內存文件信息
將文件復制本地,直接拖拽到windbg中。(windbg直接在window 應用商店下載即可)

3、獲取進程內耗時線程
在0:000 輸入框中輸入!runaway 敲回車,獲取線程占用cpu時間


4、獲取線程的棧信息
從cpu的線程占用時間來看,線程64,89,96,95,90占用的時間最長,可以初步斷定問題就出現在這幾個線程中。輸入:~64s進入該線程(64代表的是線程id)

進入該線程后就可以加載線程的棧信息了,在命令框中輸入!CLRStack 。如果出現
圖示信息說明需要單獨加載SOS.dll 文件。使用everything軟件全局搜索該dll文件位置,然后在輸入框中:.load 全路徑\SOS.dll。加載完成再次輸入:!CLRStack 就可以正常顯示棧信息了。

5、問題定位
從棧信息來看,線程一直停留在:System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].FindEntry(System.__Canon)這個方法里面,調用這個方法的類是:Aop.Api.Parser.AopJsonParser,這個類是支付寶官方sdk中的。先看FindEntry這個方法干了啥。直接官網查看源碼:
private int FindEntry(TKey key) { if( key == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); } if (buckets != null) { int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; for (int i = buckets[hashCode % buckets.Length]; i >= 0; i = entries[i].next) { if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) return i; } } return -1; }
從源碼來就是判斷key是否存在,看來就是死循環for里面了。再來看這個dictionary是用來干啥了的,反編譯Aop dll文件
private static Dictionary<string, AopAttribute> GetAopAttributes(Type type) { Dictionary<string, AopAttribute> dictionary = null; if (!AopJsonParser<T>.attrs.TryGetValue(type.FullName, out dictionary) || (dictionary == null)) { dictionary = new Dictionary<string, AopAttribute>();
private static readonly Dictionary<string, Dictionary<string, AopAttribute>> attrs;
從源碼可以看見定義了一個靜態Dictionary attrs變量,既然是靜態變量,會出現多線程競爭問題。官網也明確說明Dictionary 是非線程安全的,如果多線程讀寫需要自己去寫程序保證線程安全或者使用ConcurrentDictionary。
6、問題
問題的根本原因是多線程多寫非線程安全Dictionary,導致在FindEntry方法死循環。
