由於.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