由于.NET的垃圾回收机制至关完善,一般状况下咱们是不须要关心内存泄漏的。问题人一但傻起来,连本身都会惧怕,几个页面跳啊跳的,内存蹭蹭的往上涨,拉都拉不住。这种时候咱们就须要冷静下来,泡一杯热巧克力。再打开Visual Studio 2015的Diagnostic Tools,来检查下到底哪段代码出了问题。git
咱们先建立一个简单的UWP工程,该工程只有2个几乎为空的Page。MainPage只有两个按钮,分别用来跳转到SecondPage,以及调用GC.Collect()方法。而SecondPage就只有一个Goback用的按钮,同时在SecondPage的构造函数里建立了一个将近400MB的超大ArrayList。github
<Page x:Class="EventMemoryLeak.MainPage" …… > <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" VerticalAlignment="Center"> <Button Click="Button_Click">Go to second page</Button> <Button Click="Button_Click_1">Force GC</Button> </StackPanel> </Page> public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { this.Frame.Navigate(typeof(SecondPage)); } private void Button_Click_1(object sender, RoutedEventArgs e) { GC.Collect(); } } <Page x:Class="EventMemoryLeak.SecondPage" …… > <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Button Click="Button_Click">Go back to main page</Button> </Grid> </Page> public sealed partial class SecondPage : Page { public ArrayList arrayList { get; set; } public SecondPage() { this.InitializeComponent(); arrayList = new ArrayList(100000000); } private void Button_Click(object sender, RoutedEventArgs e) { this.Frame.GoBack(); } }
在Visual Studio 2015中Debug UWP程序时,会自动打开Diagnostic Tools的窗口(没打开也不要紧,能够经过Debug->Show Diagnostic Tools找到)。微信
每从MainPage跳转SecondPage后,内存都会明显的增长。 ide
在我写下上面这段话以后,再回到运行中的程序,在MainPage点击“Force GC“按钮后,CLR很给面子作了一次完全的回收,内存占用回到了程序刚打开的状态。这里须要说明的是,调用GC.Collect方法并不能保证当即回收全部引用计数为0的对象且释放全部内存。CLR会本身判断该怎么回收,回收多少,根本就是傲娇的小公举。函数
那是否是说傲娇的Diagnostic Tools不靠谱呢?非也!首先调用GC.Collect方法,回收是必定会被执行的,一定会有一部分的对象被释放,这部分变化咱们能够经过Snapshot很好的进行观察(后面会介绍)。其次,若是确实须要进行比较完全的回收,根据我的经验,连续调用2到3次GC.Collect方法,效果仍是很好的。再傲娇的小公举连续收到“在么”的微信,也会回复“呵呵,睡觉了”意思一下的。性能
接下来咱们要故意制造严重的内存泄漏,并用Diagnostic Tools来进行观察。咱们增长一个Service层的类,并在SecondPage中监听Service层的事件。同时我将SecondPage建立的ArraryList从400MB改成40MB,由于我主打轻薄的笔记本性能没法支撑。this
public class FakeService { public static FakeService Instance = new FakeService(); public event EventHandler ShowMeTheMoneyEvent; private FakeService() { } } public SecondPage() { this.InitializeComponent(); arrayList = new ArrayList(10000000); FakeService.Instance.ShowMeTheMoneyEvent += Instance_ShowMeTheMoneyEvent; }
这回你会发现,不管你怎么样GC(怎么感受这个名字有点污……算了我什么都不知道),内存都不会降低了。这是由于SecondPage被FakeService所引用,FakeService又是静态的存活于整个APP生命周期的对象,因此SecondPage不再会被回收释放了。哎呀个人妈呀……spa
先别急着叫,用Snapshot在比较一下内存对象,会有更可怕的事情发生。咱们从新运行该程序,在第一次运行到MainPage时,作一次Snapshot。反复的打开3次SeconcdPage,再返回MainPage作第二次的SnapShot。3d
能够看到对象相对于第一次SnapShot仅增长了43个,但Heap Size已经惨不忍睹了。点击(+43)会打开详细的对象列表。通常状况下,我会在右上角填写命名空间来缩小观察的范围。咱们这里会惊讶的发现SecondPage对象,在3次打开该页面后,居然有3份重复的实例存在。code
点击列表中的SecondPage一行,在屏幕下方的窗口中,会显示Path to Root的相关状况,能够看到SecondPage对象都由EventHandler关联到了FakeService对象上。
至此,咱们经过Diagnostic Tools就找到了内存泄漏的缘由,处理方法也很简单,在离开页面时,取消对事件的监听就好了,这里咱们能够在页面的OnNavigateFrom方法里来作。
protected override void OnNavigatedFrom(NavigationEventArgs e) { base.OnNavigatedFrom(e); FakeService.Instance.ShowMeTheMoneyEvent -= Instance_ShowMeTheMoneyEvent; }
本篇咱们简单的讨论如何使用Diagnostic Tools来观察内存对象,并就监听静态对象的事件引发的内存泄漏举例给出了解决方案。但愿可以抛砖引玉,引出许多真知灼见,最不济您也点个推荐呗。
GayHub:
https://github.com/manupstairs/UWPSamples/tree/master/UWPSamples/EventMemoryLeak