Silverlight内存泄露(七)Command
MVVM中View与ViewModel是强引用的双向关联关系,容易发生内存泄露,任何一方没有被回收都会导致另一方不能被GC回收。
Comand连接了View与ViewModel,容易产生内存泄露。
发现内存泄露
在几个View间导航几次,两次导航到View页面,获取内存快照,发现Info有两个实例。内存没有被释放。
为了每次导航到消息页面,都保存上一次显示的信息,InfoViewModel采用了单例模式,作为缓存,InfoViewModel只有一个实例,按照设想Info也应该只有一个实例。
代码:
var lazyViewModelMapping= LazyViewModelExports.FirstOrDefault(o => o.Metadata.Key.Equals(relativeUri.Host, StringComparison.OrdinalIgnoreCase));
viewModel = lazyViewModelMapping.Value;
var viewFactory = viewMapping.CreateExport();
view = viewFactory.Value as Control;
viewFactory.Dispose();//释放View内存
view.DataContext = viewModel;
是什么原因造成viewFactory.Dispose();没有释放View的内存呢?
确定泄露原因
看下面两张Info图:
正常图:
发生泄露图:View是当前页面:
发生泄露图:View不是当前页面:
可看出stopDownloadCommand在View与ViewModel间建立了强引用关系,遇到ViewModel这个单例模式(生存期与应用程序一样)造成View不能被回收。
解决内存泄露
断开stopDownloadCommand内存得到释放,从上图可看出断开这个command并没有从根本解决问题,增加其他Command仍会产生问题。
根本原因是ViewModel在此不应该使用单例模式,Lazy创建了单例,而应该缓存Modle。
弹出窗口是最好的方式,由于Silverlight4不支持子窗口,在此使用单例作为缓存,没想到造成View不能被释放。
总结
a) 绑定View的类(如VM),使用单例或缓存VM需要谨慎。VM的长生命周期将导致View不能被释放,并且每次导航到View,View的实例数都将增加。
b) 从内存角度看单例,单例中的Command、Event非常容易造成内存泄露,根源往往是单例而不是事件或command。
c)ANTS 展现的内存图并不全面,经常需要多张图结合看问题。比如,当View是当前页面时,不能看出Lazy<T>产生的问题,而需要跳转到View不是当前页面再看另外的快照。
d)ANTS 的快照图,对一类引用进行了归类,比如stopDownloadCommand并不是ViewModle中唯一的Command,还有reDownloadCommand也引用了View,图上并没有表现出来,在代码中修正了stopDownloadCommand的引用问题,View仍被reDownloadCommand引用而不能释放。