且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

UWP开发入门(十三)——用Diagnostic Tool检查内存泄漏

更新时间:2022-03-25 13:08:02

原文:UWP开发入门(十三)——用Diagnostic Tool检查内存泄漏

  因为.NET的垃圾回收机制相当完善,通常情况下我们是不需要关心内存泄漏的。问题人一但傻起来,连自己都会害怕,几个页面跳啊跳的,内存蹭蹭的往上涨,拉都拉不住。这种时候我们就需要冷静下来,泡一杯热巧克力。再打开Visual Studio 2015Diagnostic Tools,来检查下到底哪段代码出了问题。

  我们先创建一个简单的UWP工程,该工程只有2个几乎为空的PageMainPage只有两个按钮,分别用来跳转到SecondPage,以及调用GC.Collect()方法。而SecondPage就只有一个Goback用的按钮,同时在SecondPage的构造函数里创建了一个将近400MB的超大ArrayList

<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 2015Debug UWP程序时,会自动打开Diagnostic Tools的窗口(没打开也没关系,可以通过Debug->Show Diagnostic Tools找到)。

  每从MainPage跳转SecondPage后,内存都会明显的增加。 

  UWP开发入门(十三)——用Diagnostic Tool检查内存泄漏

  在我写下上面这段话之后,再回到运行中的程序,在MainPage点击“Force GC“按钮后,CLR很给面子做了一次彻底的回收,内存占用回到了程序刚打开的状态。这里需要说明的是,调用GC.Collect方法并不能保证立即回收所有引用计数为0的对象且释放所有内存。CLR会自己判断该怎么回收,回收多少,根本就是傲娇的小公举。

  UWP开发入门(十三)——用Diagnostic Tool检查内存泄漏

  那是不是说傲娇的Diagnostic Tools不靠谱呢?非也!首先调用GC.Collect方法,回收是一定会被执行的,必定会有一部分的对象被释放,这部分变化我们可以通过Snapshot很好的进行观察(后面会介绍)。其次,如果确实需要进行比较彻底的回收,根据个人经验,连续调用23GC.Collect方法,效果还是很好的。再傲娇的小公举连续收到“在么”的微信,也会回复“呵呵,睡觉了”意思一下的。

  接下来我们要故意制造严重的内存泄漏,并用Diagnostic Tools来进行观察。我们增加一个Service层的类,并在SecondPage中监听Service层的事件。同时我将SecondPage创建的ArraryList400MB改为40MB,因为我主打轻薄的笔记本性能无法支撑。

    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;
        }

 

  UWP开发入门(十三)——用Diagnostic Tool检查内存泄漏

  这回你会发现,无论你怎么样GC(怎么感觉这个名字有点污……算了我什么都不知道),内存都不会下降了。这是因为SecondPageFakeService所引用,FakeService又是静态的存活于整个APP生命周期的对象,所以SecondPage再也不会被回收释放了。哎呀我的妈呀……

  先别急着叫,用Snapshot在比较一下内存对象,会有更可怕的事情发生。我们重新运行该程序,在第一次运行到MainPage时,做一次Snapshot。反复的打开3SeconcdPage,再返回MainPage做第二次的SnapShot

 UWP开发入门(十三)——用Diagnostic Tool检查内存泄漏

  可以看到对象相对于第一次SnapShot仅增加了43个,但Heap Size已经惨不忍睹了。点击(+43)会打开详细的对象列表。一般情况下,我会在右上角填写命名空间来缩小观察的范围。我们这里会惊讶的发现SecondPage对象,在3次打开该页面后,竟然有3份重复的实例存在。

UWP开发入门(十三)——用Diagnostic Tool检查内存泄漏

  点击列表中的SecondPage一行,在屏幕下方的窗口中,会显示Path to Root的相关情况,可以看到SecondPage对象都由EventHandler关联到了FakeService对象上。

UWP开发入门(十三)——用Diagnostic Tool检查内存泄漏

  至此,我们通过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