項目上反饋了一個問題,就是在生產環境上,用戶正常使用的過程中,出現了服務器內存突然暴漲,客戶有點慌,想找下原因。
講道理,內存如果是緩慢上漲一直不釋放的話,應該是存在內存泄漏的,這種排查起來比較困難,還得找開發一塊看;但像這種突然暴漲的,肯定是把某些大對象放到內存里了,而最有可能的,就是大查詢了,比如把幾百萬數據查出來這種,但這種一般等用戶用完這個功能內存就會降下來。
環境:IIS+.net framework。發現是w3wp進程一直在漲內存,也就是iis,確實是程序的鍋。
分析內存問題的話,一般是在持續上漲的過程中,多抓幾個dump,看看哪些對象沒釋放,便於分析,這次用戶只抓了一個dump,要不然太大了,傳到我本機也費事。
那就開始分析吧。
首先找了個本地測試環境,用windbg加載dump,加載分析文件,幸運的是,.loadby sos clr一次成功了,后續分析都沒啥問題,不用再從客戶那邊拷貝sos,clr這些文件;
這是用戶正常業務場景的dump,也不知道當時多少人用,都在干什么,既然是內存問題,先看下內存中的對象情況吧:
!dumpheap -stat
果不其然,出現了DataRow的影子,估計是有個大查詢沒跑了。但是是什么場景?哪個sql?查出來多少數據?這個得繼續分析了。
需要注意的是,抓這個dump的時候,內存3g多,dump大小也3g多,但是DataRow這個對象總共才46M,為什么要看這個對象,不看其他的呢?
要知道,分析的話,從占用大的對象開始分析是沒問題,但是得看這個對象是不是比較特殊,像是上邊占用最高的是string,1g多,但是string太普遍了,數量多也正常,而且不好分析,但DataRow這個肯定是訪問數據庫了,這種對象好分析。
再就是,為什么才46M,這個涉及到托管內存和非托管內存了。c#是基於.net的高級編程語言,所謂的托管,其實是指內存的托管,就是當寫代碼需要new一個新對象的時候,是不需要考慮內存申請與銷毀的,.net自動給你做了,所以,當你抓了一個6g的dump,可能里邊的托管內存才2g,剩余4g非托管內存里的東西從windbg是看不到的,非托管內存的增長,肯定是由托管代碼引起的,所以這個地方雖然DataRow只有46M,但是由這個引起的內存增長是不可知的。
那接下來就看看工作線程數和堆棧吧,對當時的業務場景、使用人數大概有個了解:
!threads -special
工作線程數不是很多,說明同時使用的人不多,應該不會有太大的壓力,所以可能是某一個或者某幾個線程引起的,那就看下堆棧情況,大致了解下使用場景,猜一下是哪個場景引起的:
~* e!clrstack 打印下所有線程的堆棧
本來工作線程就不多,有業務含義並且和數據庫相關的堆棧就更少了,看了下,大致有三個場景:
一個貌似是在判斷權限:
一個貌似是個財務的往來單位查詢:
還有一個貌似是財務的憑證查詢:
具體是哪個引起的就得繼續分析了,可以分別進入這三個線程,看下線程里的對象情況。
比如,切換到往來單位查詢的那個線程里(線程id是107),然后通過!dso命令查看下當前線程的對象:
~107 s
!dso
直接就看到一個sql了,看下這個線程里的DataRow吧,看看它是不是它的鍋:
!do 0000006231d5af28
查看下所在的DataTable信息:
!do 0000006027566878
從elementColumnCount能看出來,當前是查詢出來了89列;看的時候,要看下Type列,看看當前的對象類型,哪些是你需要和關注的。
然后再看recordManager,看下查詢出來的記錄信息,也就是行信息:
!do 0000006027566b08
recordCapacity,行容量,524288,即查詢出來的行為52w。
至此,應該明了了,就是往來單位查詢場景,上邊的那個sql(當然也可以通過!dso中sqlCommand對象查看Text,確認具體的sql),查詢出來了52w行,89列,導致的內存暴漲。
后來通過跟開發與用戶確認,確實是這個查詢界面上,沒有選具體的單位,然后關聯了一張600w數據的表,最終查出了52w行數據導致的。實際用戶的業務場景是需要具體單位的,這種場景沒做跨單位查詢的權限控制,用戶又恰巧沒選單位,所以出現了這個問題。后來開發把單位加了必選,該問題解決。