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后,內存都會明顯的增加。 

  

  在我寫下上面這段話之后,再回到運行中的程序,在MainPage點擊“Force GC“按鈕后,CLR很給面子做了一次徹底的回收,內存占用回到了程序剛打開的狀態。這里需要說明的是,調用GC.Collect方法並不能保證立即回收所有引用計數為0的對象且釋放所有內存。CLR會自己判斷該怎么回收,回收多少,根本就是傲嬌的小公舉。

  

  那是不是說傲嬌的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;
        }

 

  

  這回你會發現,無論你怎么樣GC(怎么感覺這個名字有點污……算了我什么都不知道),內存都不會下降了。這是因為SecondPageFakeService所引用,FakeService又是靜態的存活於整個APP生命周期的對象,所以SecondPage再也不會被回收釋放了。哎呀我的媽呀……

  先別急着叫,用Snapshot在比較一下內存對象,會有更可怕的事情發生。我們重新運行該程序,在第一次運行到MainPage時,做一次Snapshot。反復的打開3SeconcdPage,再返回MainPage做第二次的SnapShot

 

  可以看到對象相對於第一次SnapShot僅增加了43個,但Heap Size已經慘不忍睹了。點擊(+43)會打開詳細的對象列表。一般情況下,我會在右上角填寫命名空間來縮小觀察的范圍。我們這里會驚訝的發現SecondPage對象,在3次打開該頁面后,竟然有3份重復的實例存在。

  點擊列表中的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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM