(最近使用內存分析工具ANTS Memory Profiler,以及其他網友提供的意見發現最終導致內存泄漏的就是MEF,在此特地更新下,與大家分享!最下面紅色字體)
最近參考使用了郭明峰的一套架構來做新的項目架構,這套架構看起來還是不錯的,先向小郭同學的分享精神致敬!
(郭同學的項目文檔:http://www.cnblogs.com/guomingfeng/archive/2013/05/19/mvc-overall-design.html)
項目開發上線后,傻眼了,貌似沒有幾個人訪問的新項目,速度一直慢的跟牛一樣,真心沒法交差啊。上面發下話了,解決不了就可以走人了。壓力可想而知。
接下來就是苦逼的找原因了。
症狀:1、內存占用高,8g的內存很快就能吃完
2、網站相應速度慢,firebug檢測每次都是在等待
根據這些症狀,感覺是內存泄露了,於是memory profiler。。。什么性能分析工具都用上了,怎奈水平有限,不會使用這些高級玩意,一番折騰下來,就剩下無奈。
但總不能放棄,對比之前做的mvc項目,就是多了entityframework、MEF這兩樣,看樣子也就這兩塊的原因了,EF的嫌疑最大,而且首次使用,並不了解。於是,接下來又是一段苦逼的研究;功夫不負有心人,根據目前研究的結果及上線的效果來看,基本上找到了問題的所在。總結下,與諸位分享,高手忽略。
問題原因:1、很多數據查詢一次性讀入到內存中,導致內存增加。
2、上下文對讀入到內存中的數據對象會進行跟蹤,導致內存不斷攀升,疑似內存泄露
解決辦法:1、深入學習EF,監控每一步生成的sql語句,防止無用數據的調取
2、網站大部分的數據在讀取的時候是不需要進行上下文跟蹤的,一定要禁止跟蹤,否則內存就會爆了!代碼如下:
/// <summary> /// 獲取當前實體的查詢數據集(通過讀上下文進行讀取,只讀專用,返回的實體數據不會被上下文跟蹤) /// </summary> public virtual IQueryable<TEntity> ReadEntities { get { return WriteContext.Set<TEntity>().AsNoTracking(); } } /// <summary> /// 獲取當前實體的查詢數據集(通過寫上下文進行讀取,修改專用,返回的實體數據會被上下文進行跟蹤) /// </summary> public virtual IQueryable<TEntity> WriteEntities { get { return WriteContext.Set<TEntity>(); } }
說明:我在倉儲模式中,對讀取實體集合進行了修改,ReadEntities表示不需要進行跟蹤的,加上AsNoTracking方法;WriteEntities表示是需要跟蹤的,用於修改實體信息使用。這樣以來,所有的症狀全部消失了。
當然,也許有更好的優化方法,至少目前本人發現了這些問題所在,而且微軟的白皮書中確實也講的了EF的性能注意事項,怎奈倉促開發,對EF毫不了解,罪過罪過!大家引以為鑒!
另外,還遇到一個問題,不知道是否是DBcontext線程沖突問題,大家幫分析下,謝謝:
持續的內存泄漏問題終於有了結果了,花了近一周的時間,無數苦逼的熬夜,終於掌握了ANTS Memory Profiler的基本用法,結合之前網友的寶貴意見,經檢測,終於發現導致內存泄漏的罪魁禍首:MEF。
小郭的架構中有這么一段代碼:
public CompositionContainer Container { get { CompositionContainer container; if (!HttpContext.Current.Items.Contains(MefContainerKey)) { container = new CompositionContainer(_catalog, CompositionOptions.DisableSilentRejection); HttpContext.Current.Items.Add(MefContainerKey, container);
HttpContext.Current.DisposeOnPipelineCompleted(container); } else { container = (CompositionContainer)HttpContext.Current.Items[MefContainerKey]; } return container; } }
這段代碼用於創建MEF容器,並向其中加入對象實例化的目錄。
小郭童鞋說了,項目中的所有Import的對象都是MEF創建的,不需要刻意的去Dispose,只要MEF對象釋放了,那么container中的所有實例化對象就會被釋放。
話沒錯,但代碼中確實沒有看到有釋放container對象的地方,我翻遍了評論,看到有個哥們ltcszk提醒說要加入HttpContext.Current.DisposeOnPipelineCompleted(container)這句話就ok,當時並未重視,固執的認為是過多的DBContext導致的,這兩天經過ANTS Memory Profiler測試下,果真是這個問題導致的,加上這句話,效果立竿見影,在此感謝ltcszk的提醒。同時總結下,與諸位分享!
在頁面打開后,理論上這次請求就完畢,所有線程中創建的對象就應該被GC釋放掉,Profiler在每次快照的時候就會執行GC的回收,那么實際的情況我們看下
優化前:
內存狀況:
第一步
第二步
第三步
優化后:
駐留在內存中的類型明顯少了很多,只有35個,大部分是cache內容!
在此記錄,與諸位分享,歡迎拍磚!