對象的建立可以使用new,也可以使用IOC架如:castle、MEF等,IOC建立的對象的生命週期,可能IOC負責管理,使用架構的開發人員如果不弄清楚可能會造成記憶體泄露問題。
這些記憶體泄露問題並不是IOC架構的bug,只是開發人員使用不當或者不注意造成的記憶體泄露問題。
以MEF為例說明我碰到的兩種記憶體泄露問題。
記憶體泄露系列閱讀提示:
一摸一樣的對象圖,有時候我們可以認為它是記憶體泄露,有時候又認為它不是記憶體泄露,這一切只是由於上下文不同,這一系列文章中ANTS Memoery Profle都是有特定上下文,單獨看完全沒有意義。如何確定是記憶體泄露?可以參考前面的文章。
對象以圖的形式存在,Ants Memory Profile為了分析方便把這些圖處理為樹,讓我們可以把注意力集中到分析的對象。但我們必須明白記憶體中對象關係構成圖,也就是說ANTS的樹狀圖只是記憶體中對象分布的一個局部,分析記憶體泄露時必須有全域觀念,需要相關的幾張圖一起看,即使一張圖也要整體看,這樣才能分析記憶體泄露問題。
由於看一張圖沒什麼意義,如果把多張圖都貼出來,這文章就太難寫了,即使多張圖都貼出來,也不一定能表達清楚,分析記憶體泄露最重要的是經驗。接下來的幾篇會減少甚至不用ANTS圖。
ExportLifetimeContext使用不當造成記憶體泄露
ExportLifetimeContext 需要調用Dispose方法釋放由MEF管理的對象,否則對象不會被釋放。
MSDN:
Disposing of a ExportLifetimeContext(Of T) object calls the referenced method to release its associated export.
Call Dispose when you are finished using the ExportLifetimeContext(Of T). The Dispose method leaves the ExportLifetimeContext(Of T) in an unusable state. After calling Dispose, you must release all references to the ExportLifetimeContext(Of T) so the garbage collector can reclaim the memory that theExportLifetimeContext(Of T) was occupying.
可以看到ViewModel存在多個執行個體,可能出現了記憶體泄露。
經過分析找出發生記憶體泄露的對象圖:
可以看到MEF的DisposableReflectionComposablePart一直保持著ViewModel的引用,造成不能釋放ViewModel。
MVVM使用自訂導航,代碼:
[Export]
public class CompositionNavigationContentLoader : INavigationContentLoader
{
public CompositionNavigationContentLoader()
{
}
[ImportMany(typeof(IView))]
public IEnumerable<ExportFactory<IView, IViewMetadata>> ViewExports { get; set; }
[ImportMany(typeof(IViewModel),RequiredCreationPolicy = CreationPolicy.Any)]
public List<ExportFactory<IViewModel, IViewModelMetadata>> ViewModelExports { get; set; }
[ImportMany("lazy", typeof(IViewModel), RequiredCreationPolicy = CreationPolicy.Any)]
public List<Lazy<IViewModel, IViewModelMetadata>> LazyViewModelExports { get; set; }
public IAsyncResult BeginLoad(Uri targetUri, Uri currentUri, AsyncCallback userCallback, object asyncState)
{
var viewModelMapping = ViewModelExports.FirstOrDefault(o => o.Metadata.Key.Equals(relativeUri.Host, StringComparison.OrdinalIgnoreCase));
//ViewModel
var viewModelFactory = viewModelMapping.CreateExport();
viewModel = viewModelFactory.Value as IViewModel;
viewModelFactory.Dispose();//釋放對象
//View
var viewFactory = viewMapping.CreateExport();
view = viewFactory.Value as Control;
viewFactory.Dispose(); //釋放對象
//綁定、導航
view.DataContext = viewModel;
var values = viewModelMapping.Metadata.GetArgumentValues(targetUri);
viewModel.OnNavigated(values);
}
}
如果不加上viewModelFactory.Dispose();
對象MEF建立的對象不會被回收,VIew、ViewModel及其引用的資源都一直保持到程式關閉。
結論
a) 不要盲目使用第三方架構。
b) 記憶體泄露都有上下文,只有特定上下文中Managed 程式碼才可能產生記憶體問題。
c)注意由IOC建立的對象生命週期,如果IOC建立的對象由容器管理生命期,可能需要調用IOC提供的相關方法執行對象的銷毀。