本文章是由機器翻譯。

C#、Visual Basic 和 C++

在 Windows 市集應用程式中管理記憶體,第 2 篇

Chipalo Street
Dan Taylor

 

Windows 8 國特別版的 MSDN 雜誌,這個系列的第一篇文章討論了如何出現記憶體洩漏,為什麼他們減慢您的應用程式,並會降低整個系統的經驗、 常規的方法來避免洩露,具體問題,有 JavaScript 程式有問題 (請參閱"管理記憶體在 Windows 存儲應用," msdn.microsoft.com/magazine/jj651575)。現在我們來看看記憶體洩漏 C#、 Visual Basic 和 c + + 應用程式的上下文中。我們會分析洩漏發生在過去幾代應用程式和 Windows 8 的技術如何説明您避免這些情況的一些基本方法。有了這個基礎,我們就會移動到更複雜的方案,可使您對洩漏記憶體的應用程式。讓我們回到它 !

簡單迴圈

在過去,許多洩漏而引致的引用週期。即使在週期中的物件可能永遠無法達到迴圈中所涉及的物件總是有積極的參考。活動參考將物件活著永遠保持,並如果程式創建這些週期頻繁,它將繼續隨著時間的推移洩漏記憶體。

引用週期可以出現多個原因。最明顯的是相互顯式引用物件時。例如,下面的代碼將導致圖片中圖 1

Foo a = new Foo();
Bar b = new Bar();
a.barVar = b;
b.fooVar = a;

A Circular Reference
圖 1 循環參考

值得慶倖的是,在進行垃圾回收的語言,如 C#、 JavaScript 和 Visual Basic,這種循環參考將會自動清除一旦不再需要這些變數。

C + + / CX,相比之下,並不使用垃圾回收。相反,它依賴于要執行的記憶體管理的引用計數。這意味著,只有當他們有零活動的引用時,將由系統回收物件。在這些語言中的這些物件之間的週期將迫使 A 和 B,永遠住,因為他們不可能有零參照。更糟的是,一切都引用的 A 和 B 將永遠以及生活。這是簡化的示例,可以輕鬆地避免時編寫基本的程式 ; 但是,複雜的程式可以創建涉及非顯而易見的方式將連結在一起的多個物件的週期。讓我們看看一些例子。

週期與事件處理常式

因為在前一篇文章中討論,事件處理常式循環參考要創建的一種極為常見方法。圖 2 顯示這情況可能如何發生。

圖 2 造成循環參考同一個事件處理常式

<MainPage x:Class="App.MainPage" ...>
  ...
<TextBlock x:Name="displayTextBlock" ...
/>
  <Button x:Name="myButton" Click="ButtonClick" ...
/>
  ...
</MainPage>
public sealed partial class MainPage : Page
{
  ...
private void ButtonClick(object sender, RoutedEventArgs e)
  {
    DateTime currentTime = DateTime.Now;
    this.displayTextBlock.Text = currentTime.ToString();
  }
  ...
}

在這裡我們只需添加一個按鈕和文字區塊頁。 我們還設置已定義頁類,為該按鈕的 Click 事件的事件處理常式。 此處理程式更新中 TextBlock 以顯示目前時間只要按一下該按鈕的文本。 正如您所看到的即使這個簡單的示例具有循環參考。

按鈕和 TextBlock 頁的兒童,因此該頁面必須具有對其引用的頂部圖中所示圖 3

A Circular Reference Related to the Event Handler
圖 3 循環參考有關的事件處理常式

在下圖中,頁類定義的事件處理常式的註冊創建另一個引用。

事件源 (按鈕) 具有事件處理常式中,委託方法的強引用,以便源可以觸發事件時調用的事件處理常式。 讓我們來調用此委託一個強有力的委託,就因為它引用的是強一樣。

現在,我們有一個循環參考。 一旦使用者在其他頁面導航,垃圾回收器 (GC) 是夠聰明,回收的頁面和按鈕之間的迴圈。 當不再需要他們如果您在 JavaScript,C# 或 Visual Basic 編寫應用程式時,將自動清除了這些類型的循環參考。 正如我們說較早前,然而,C + + / CX 是 ref 計數的語言,這意味著他們引用計數降到零的情況下,才會自動刪除物件。 在這裡,創建的強引用將強制頁面和按鈕以永遠活著因為他們不可能有零引用計數。 更糟的是,所有由頁 (可能非常大元素樹) 所載的專案會永遠活以及因為頁面保存到所有這些物件的引用。

當然,創建事件 handers 是極為常見的方案,微軟並不希望這會洩漏導致在您的應用程式使用的語言無關。 為此原因,XAML 編譯器使的委託對事件攔截器的引用弱引用。 因為從該委託的引用是弱引用你可以此認為作為一個弱的委託。

弱委託確保頁面不保持活動狀態,對頁的委託的引用。 弱引用將不計入該頁面的引用計數,並因此將使它能夠被摧毀後的所有其他引用降至零。 隨後,該按鈕、 TextBlock 和任何其他引用的頁將被銷毀以及。

長壽命的事件源

有時具有使用壽命長的物件定義的事件。 我們引用這些事件作為長壽命,因為事件共用物件定義它們的存留期。 這些長壽事件保持對所有註冊的處理常式的引用。 這將強制處理常式和目標的從業員,要活著,只要長壽事件源物件。

在上一篇文章中的記憶體洩漏"事件處理常式"部分中,我們分析了這樣一件事。 在應用程式中的每個頁面註冊的應用程式視窗的 SizeChangedEvent。 從視窗的 SizeChangedEvent 到頁面上的事件處理常式的引用將保持頁面的每個實例還活著,只要周圍是應用程式的視窗。 所有要仍然活著即使只有一個被流覽的網頁是在視圖中。 此洩漏很容易修復通過登出每個頁 SizeChangedEvent 處理常式,當使用者導航其他頁面。

在該示例中,它是明確,當不再需要頁開發人員能夠登出從頁中的事件處理常式。 不幸的是不總是容易到物件的存留期的原因。 考慮在 C# 或 Visual Basic 類比"弱委託",如果您發現長壽持有通過事件處理常式的物件的事件引起的洩漏。 (看"類比 '弱代表' 在 CLR 中" bit.ly/SUqw72.)弱委託圖案置於一個中間物件之間的事件源和事件處理常式。 如中所示從事件源到中間物件和對事件處理常式中,從中間物件的弱引用使用的強引用圖 4

Using an Intermediate Object Between the Event Source and the Event Listener
圖 4 使用事件源和事件攔截器之間的一個中間物件

上圖中圖 4、 LongLivedObject 公開往屆和 ShortLivedObject 註冊 EventAHandler 對事件進行處理。 LongLivedObject 已比激進的多更大的生命跨度­物件和往屆和 EventAHandler 之間的強引用將保持 ShortLivedObject 只要在 LongLivedObject。 配售 IntermediateObject LongLivedObject 和 ShortLivedObject (底部圖中所示) 之間允許 IntermediateObject,而 ShortLivedObject 不會外泄。 這是很多小的洩漏,因為 IntermediateObject 需要公開只有一個函數,而 ShortLivedObject 可能包含大型資料結構或複雜的視覺化樹。

讓我們看看如何可以在代碼中實現弱的委託。 許多類可能要註冊為的事件是顯示­Properties.OrientationChanged。 DisplayProperties 是實際上是一個靜態的類,因此 OrientationChanged 事件將永遠周圍。 該事件將舉行對您使用偵聽事件的每個物件的引用。 在示例中所示圖 5圖 6,LargeClass 類使用弱委派模式,確保 OrientationChanged 事件舉行只有中間類的強引用時註冊事件處理常式。 中級班然後調用該方法,定義的 LargeClass,OrientationChanged 事件觸發時的實際做必要的工作。

The Weak Delegate Pattern
圖 5 弱委託模式

圖 6 執行薄弱的委託

public class LargeClass
{
  public LargeClass()
  {
    // Create the intermediate object
    WeakDelegateWrapper wrapper = new WeakDelegateWrapper(this);
    // Register the handler on the intermediate with
    // DisplayProperties.OrientationChanged instead of
    // the handler on LargeClass
    Windows.Graphics.Display.DisplayProperties.OrientationChanged +=
      wrapper.WeakOrientationChangedHandler;
  }
  void OrientationChangedHandler(object sender)
  {
    // Do some stuff
  }
  class WeakDelegateWrapper : WeakReference<LargeClass>
  {
    DisplayPropertiesEventHandler wrappedHandler;
    public WeakDelegateWrapper(LargeClass wrappedObject,
      DisplayPropertiesEventHandler handler) : base(wrappedObject)
    {
      wrappedHandler = handler;
      wrappedHandler += WeakOrientationChangedHandler;
    }
    public void WeakOrientationChangedHandler(object sender)
    {
      LargeClass wrappedObject = Target;
      // Call the real event handler on LargeClass if it still exists
      // and has not been garbage collected.
Remove the event handler
      // if LargeClass has been garbage collected so that the weak
      // delegate no longer leaks
      if(wrappedObject != null)
        wrappedObject.OrientationChangedHandler(sender);  
      else
        wrappedHandler -= WeakOrientationChangedHandler;
    }
  }
}

Lambda

很多人覺得更容易實現事件處理常式與 lambda — — 或內聯函數 — — 而不是一種方法。 讓我們轉換的示例從圖 2 正是這樣做 (見圖 7)。

執行事件處理常式與 Lambda 圖 7

<MainPage x:Class="App.MainPage" ...>
  ...
<TextBlock x:Name="displayTextBlock" ...
/>
  <Button x:Name="myButton" ...
/>
  ...
</MainPage>
public sealed partial class MainPage : Page
{
  ...
protected override void OnNavigatedTo
  {
    myButton.Click += => (source, e)
    {
      DateTime currentTime = DateTime.Now;
      this.displayTextBlock.Text = currentTime.ToString();
    }
  ...
}

使用 lambda 還創建了一個迴圈。 第一次引用從頁按鈕和文字區塊仍明顯地創建 (喜歡的頂部圖中圖 3)。

下一組中所示的引用圖 8,以不可見方式創建的 lambda。 該按鈕的 Click 事件掛接到其調用方法由封閉由編譯器創建的內建物件上實現一個 RoutedEventHandler 物件。 關閉必須包含對 lambda 所引用的所有變數的引用。 這些變數之一就是"這、 其中 — — 在 lambda — — 到頁中,從而創建循環參考。

References Created by the Lambda
圖 8 引用創建的 Lambda

如果在 C# 或 Visual Basic 編寫 lambda,CLR GC 將回收在此週期中涉及到的資源。 但是,在 C + + / CX 這種引用是一個強烈的引用,將會導致洩漏。 這並不意味著所有 lambda 在 C + + / CX 洩漏。 如果我們沒引用"這"的僅用於關閉的本地變數定義 lambda 時,循環參考不會有被創建。 作為一個解決這個問題,如果您需要訪問外部關閉內聯事件處理常式中的變數,該事件處理常式作為實現方法相反。 這允許 XAML 編譯器創建的弱引用從事件創建事件處理常式,並將回收的記憶體。 另一個選擇是使用指向成員的指標語法,允許您指定是否對包含成員的指標方法 (在本例中,頁面) 的類採取強或弱引用。

使用事件 Sender 參數

如中所述前一條,每個事件處理常式接收參數,通常稱為"寄件者,"它表示事件源。 事件源參數的 lambda 有助於避免循環參考。 讓我們來修改我們的示例中 (使用 C + + / CX) 所以按鈕會顯示按一下時的目前時間 (見圖 9)。

圖 9 令該按鈕顯示目前時間

<MainPage x:Class="App.MainPage" ...>
  ...
<Button x:Name="myButton" ...
/>
  ...
</MainPage>
MainPage::MainPage()
{
   ...
myButton->Click += ref new RoutedEventHandler(
     [this](Platform::Object^ sender, 
     Windows::UI::Xaml::RoutedEventArgs^ e)
   {    
     Calendar^ cal = ref new Calendar();
     cal->SetToNow() ;
     this->myButton->Content = cal->SecondAsString();
   });
   ...
}

更新的 lambda 創建相同的循環參考所示圖 8。 他們將導致 C + + / CX 洩漏,但這可以避免通過引用 myButton 通過"這個"變數而不使用源參數。 當執行關閉方法時,它就在堆疊上創建的"源"和"e"的參數。 這些變數生活僅用於為而不是在方法調用期間,只要 lambda 附加到該按鈕的事件處理常式 (currentTime 具有相同的壽命)。 這裡是要使用的源參數的代碼:

MainPage::MainPage()
{
  ...
myButton->Click += ref new RoutedEventHandler([](Platform::Object^ sender,
  Windows::UI::Xaml::RoutedEventArgs^ e)
  {    
    DateTime currentTime ;
    Calendar^ cal = ref new Calendar();
    cal->SetToNow() ;
    Button ^btn = (Button^)sender ;
    btn->Content = cal->SecondAsString();  });
  ...
}

引用現在看上去像什麼示圖 10。 顯示為紅色,創建循環參考的是目前只在事件處理常式的執行過程。 一旦該事件處理常式已經完成,我們留下來的將會導致洩漏沒有迴圈,此引用已被破壞。

Using the Source Parameter
圖 10 使用源參數

使用 WRL,以避免在標準 c + + 代碼中洩漏

您可以使用標準 c + + 創建 Windows 存儲的應用程式,除了 JavaScript,C#,C + + / CX 和 Visual Basic。 當這樣做的時候,被採用熟悉 COM 技術,如引用計數來管理物件的存留期和測試 HRESULT 值,以確定是否成功或失敗的操作。 Windows 運行時 c + + 範本庫 (WRL) 簡化了編寫此代碼的過程 (bit.ly/P1rZrd)。 我們推薦您使用它實施標準 c + + Windows 存儲應用程式時,減少任何 bug 和記憶體洩漏,可以是極難找到並解決。

使用跨語言界限謹慎的事件處理常式

最後,是需要特別注意的一個編碼模式。 我們討論了從循環參考,涉及的事件處理常式,和許多情況下可以檢測到並通過平臺提供的緩解措施來避免洩漏的可能性。 這些緩解不適,週期跨越多個垃圾回收堆時。

讓我們看看如何可能發生此錯誤,如中所示圖 11

圖 11 顯示使用者的位置

<Page x:Class="App.MainPage" ...>
  ...
<TextBlock x:Name="displayTextBlock" ...
/>
  ...
</Page>
public sealed partial class MyPage : Page
{
  ...
Geolocator gl;
  protected override void OnNavigatedTo{} ()
  {
    Geolocator gl = new Geolocator();
    gl.PositionChanged += UpdatePosition;
  }
  private void UpdatePosition(object sender, RoutedEventArgs e)
  {
    // Change the text of the TextBlock to reflect the current position
  }
  ...
}

這個例子是非常類似于前面的示例。 頁中包含 TextBlock 顯示一些資訊。 在此示例中,雖然 TextBlock 顯示使用者的位置,如下所示的圖 12

Circular References Span a Garbage-Collector Boundary
圖 12 循環參考跨越垃圾回收器邊界

此時,您可能可以繪製循環參考自己。 什麼是不明顯,但是,是循環參考跨越垃圾回收器的邊界。 因為引用擴展以外的 CLR,CLR GC 無法檢測週期的存在,這會洩漏。 它很難防止這些類型的洩漏,因為你不能總是告訴在哪種語言中的物件和它的事件實施。 如果 Geolocator 編寫的 C# 或 Visual Basic,循環參考將呆在 CLR 中和迴圈會被垃圾回收。 如果該類編寫的 c + + (如本例中) 或 JavaScript,迴圈將導致洩漏。

有幾種方法可以確保您的應用程式不受這樣的洩漏。 首先,你不需要擔心這些洩漏,如果你在寫一個純 JavaScript 程式。 JavaScript GC 往往是足夠智慧,可以跨所有 WinRT 物件跟蹤循環參考。 (請參閱前一條 JavaScript 記憶體管理有關的更多詳細資訊。)

您也不必擔心如果你知道的物件的事件在 XAML 框架正在註冊。 這意味著 Windows.UI.Xaml 命名空間中的任何內容,包括所有熟悉的 FrameworkElement、 UIElement 和控制項類。 CLR GC 是夠聰明,跟蹤通過 XAML 物件的循環參考。

另一種方法處理這種類型是洩漏的要登出的事件處理常式,當不再需要。 在此示例中,您可以登出 OnNavigatedFrom 事件的事件處理常式。 創建事件處理常式的引用將被刪除,所有的物件將被銷毀。 請注意不可能要登出 lambda,所以處理具有 lambda 事件導致洩漏。

分析中使用 C# 和 Visual Basic 的 Windows 存儲應用程式的記憶體洩漏

如果您編寫 C# 或 Visual Basic 中的視窗應用程式商店,它非常有用地注意到許多技術討論在前一條上 JavaScript 適用于 C# 和 Visual Basic 也。 尤其是,弱引用的使用是一種常見而有效的方法,以減少記憶體增長 (見 bit.ly/S9gVZW 的詳細資訊),和"處置"和"膨脹"體系結構模式同樣也適用。

現在讓我們看看如何可以查找和修復常見使用洩漏的採取工具可用今日:Windows 工作管理員和託管的代碼分析工具稱為的 PerfView,供您在下載 bit.ly/UTdb4M

"事件處理常式"一節中前一條 上洩漏,我們看一個稱為 LeakyApp 的例子 (一再在 圖 13 為了您的方便),這將導致記憶體洩漏其視窗的 SizeChanged 事件處理常式中。

圖 13 LeakyApp

public sealed partial class ItemDetailPage : LeakyApp.Common.LayoutAwarePage
  {
    public ItemDetailPage()
    {
      this.InitializeComponent();
    }
    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
      base.OnNavigatedTo(e);
      Window.Current.SizeChanged += WindowSizeChanged;
    }
    private void WindowSizeChanged(object sender,
      Windows.UI.Core.WindowSizeChangedEventArgs e)
    {
      // Respond to size change
    }
// Other code
  }

在我們的經驗,這是最常見類型的 C# 和 Visual Basic 代碼中洩漏,但我們將介紹的技術也適用于圓形事件洩漏和無界的資料結構的增長。 讓我們看關於如何查找並修復您自己今天使用可用的工具的應用程式中的洩漏。

尋找記憶體增長

固定記憶體洩漏的第一步是確定穩定記憶體增長從應是中性的記憶體的行動。 "發現記憶體洩漏"一節中前一條,我們討論了非常簡單的方法,您可以使用內置的 Windows 工作管理員監視的增長的總工作設置 (TWS) 的應用程式通過多次運行的應用場景。 在應用程式範例中,會導致記憶體洩漏的步驟是以圖塊上按一下,然後返回到主頁導航。

圖 14、 第一張截圖顯示的工作集工作管理員中前 10 反覆運算的這些步驟,和底部的螢幕擷取畫面顯示了這 10 次反覆運算之後。

Watching for Memory Growth

圖 14 監視記憶體增長

10 次反覆運算後,您可以看到,使用的記憶體量從 44,404 K 增至 108、 644 K。 這肯定看起來像記憶體洩漏,我們應進一步挖掘。

添加 GC 決定論

是我們在我們的手上有記憶體洩漏,我們需要確認它完整垃圾收集清理後仍然存在。 GC 使用一組啟發方式來決定最佳時間運行並回收死了記憶體,它通常不會一份好工作。 然而,在任何給定的時間有可能是"死"物件在記憶體中沒有尚未收集到的一些。 明確調用 GC 使我們能夠單獨的增長緩慢的集合和增長造成的真正洩漏造成的它清除圖片,當我們看,調查物件真正漏。

這樣做的最簡單方法是使用從內 PerfView 下, 一節上堆快照的說明中所示的"武力 GC"按鈕。 另一個選項是將按鈕添加到您的應用程式將會觸發使用代碼的 Gc。 下面的代碼會誘使垃圾回收:

private void GCButton_Click(object sender, RoutedEventArgs e)
{
  GC.Collect();
  GC.WaitForPendingFinalizers();
  GC.Collect();
}

WaitForPendingFinalizers 和收集的後續調用確保釋放因終端子的任何物件將獲得以及收集。

在應用程式範例中,但是,釋放僅 7 MB 的工作集的 108 MB 的 10 次反覆運算後按一下此按鈕。 此時我們可以確信在 LeakyApp 中有記憶體洩漏。 現在,我們需要尋找在託管代碼中的記憶體洩漏的原因。

分析記憶體增長

現在我們將使用 PerfView 來採取的 CLR 的 GC 堆,比較和分析 diff 來查找洩漏的物件。

要找出被洩露的記憶體的你會想要堆的快照之前和之後運行通過洩漏-­在您的應用程式中導致行動。 使用 PerfView,您可以比較兩個快照,以查找記憶體增長在哪裡。

若要與 PerfView 的堆快照:

  1. 打開 PerfView。
  2. 在功能表列中,按一下記憶體。
  3. 按一下採取堆快照 (請參見圖 15)。
  4. 從清單中選擇您的 Windows 存儲應用程式。
  5. 按一下"武力 GC"按鈕,以誘使您的應用程式內的 GC。
  6. 設置為您想要保存並按一下轉儲 GC 堆轉儲檔案名 (請參見圖 16)。

Taking a Heap Snapshot
圖 15 堆快照

Dumping the GC Heap
圖 16 GC 堆轉儲

託管堆的轉儲將會保存到您指定的檔,PerfView 會打開轉儲檔託管堆上顯示所有類型的清單的顯示。 記憶體洩漏調查中,應刪除折 %和 FoldPats 的文字方塊的內容,並按一下更新按鈕。 在生成的視圖中,Exc 列顯示在 GC 堆使用類型的位元組中的總大小和 Exc Ct 列在 GC 堆上顯示該類型的實例的數量。

圖 17 LeakyApp 為顯示 GC 轉儲的視圖。

A Heap Snapshot in PerfView
圖 17 堆快照中 PerfView

若要獲取顯示記憶體洩漏的兩堆快照的比較:

  1. 通過幾次反覆運算的操作導致記憶體洩漏,在您的應用程式的運行。 這將包括在基線中的任何延遲載入或一次性初始化的物件。
  2. 拍攝堆快照,包括迫使 GC 移除任何死的物件。 我們會將這稱為"之前"快照。
  3. 通過幾個運行多個反覆運算您洩漏-­導致行動。
  4. 採取另一堆快照,包括迫使 GC 移除任何死的物件。 這將是"之後"快照。
  5. 從後快照視圖,按一下比較功能表項目,然後選擇作為基準快照之前。 要確保您從打開的視圖的快照,或它不會顯示在比較功能表之前。
  6. 將顯示一個新視窗包含差異。 刪除折疊 %和 FoldPats 文字方塊中的內容和更新視圖。

你現在的視圖中顯示您託管堆的兩個快照之間的託管物件中的增長。 對於 LeakyApp,我們花了三個反覆運算後的快照和後的快照 13 反覆運算後前, 10 次反覆運算後給予 GC 堆中的差異。 從 PerfView 堆快照 diff 示圖 18

The Diff of Two Snapshots in PerfView
圖 18 中 PerfView 的兩個快照的比較

Exc 列讓在託管堆上的每個類型的總大小的增加。 然而,Exc Ct 列將顯示實例的總和中兩堆快照,而不是兩者之間的區別。 這是不是你會期望為這種分析,和 PerfView 的將來版本將允許您查看此列作為區別 ; 現在,不理 Exc Ct 列時使用比較視圖。

兩個快照間洩露任何類型將在 Exc 列中,有積極的值,但確定哪些物件被收集,防止物件會採取一些分析。

分析比較

基於您的應用程式的知識,應該看看比較的物件的清單並找到你想不到隨著時間而增長的任何類型。 因為洩漏很可能是正在舉行的由您的應用程式代碼的引用的結果,請看第一,在您的應用程式中定義的類型。 下一個地方就是在洩漏的 Windows.UI.Xaml 命名空間中類型因為這些很可能由您的應用程式代碼以及舉行的。 如果我們看第一隻在我們的應用程式中定義的類型時,ItemDetailPage 類型顯示清單的頂部附近。 它是在我們的應用程式範例中定義的最大洩密的物件。

按兩下清單中的類型將帶您到"引用-從"(原文如此) 為該類型的視圖。 此視圖顯示保存對該類型引用的所有類型的引用樹。 您可以展開該樹並單一步驟的所有都保持該類型的引用。 在樹中,值為 [CCW (物件類型)] 意味著物件正在備活著從託管代碼以外的引用 (例如 XAML 框架,c + + 或 JavaScript 代碼)。 圖 19 顯示我們可疑的 ItemDetailPage 物件的引用樹的螢幕擷取畫面。

The Reference Tree for the ItemDetailPage Type in PerfView
圖 19 中 PerfView 的 ItemDetailPage 類型的引用樹

此視圖中,可以清楚看到 ItemDetailPage 被關押活的 WindowSizeChanged 事件,該事件處理常式,這很可能是記憶體洩漏的原因。 該事件處理常式正在舉行的由託管代碼,在這種情況下以外的東西 XAML 框架。 如果你看看一個 XAML 物件,您可以看到他們正在還正在備活著相同的事件處理常式。 作為一個例子,Windows.UI.Xaml.Controls.Button 類型的引用樹中顯示圖 20

The Reference Tree for the Windows.UI.Xaml.Controls.Button Type
圖 20 Windows.UI.Xaml.Controls.Button 類型的引用樹

從這一觀點,你可以看到,所有的使用者介面的新實例。Xaml.Controls.Button 是正在 ItemDetailPage,反過來被保持活動狀態的 WindowSizeChangedEventHandler 保持活動狀態。

很清楚這一點是,要修復我們需要到 ItemDetailPage,從 SizeChanged 事件處理常式刪除該引用的記憶體洩漏就像這樣:

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
  Window.Current.SizeChanged -= WindowSizeChanged;
}

後向 ItemDetailPage 類中添加此重寫,ItemDetailPage 實例不再累積隨著時間的推移,我們洩漏固定的。

這裡介紹的方法給你一些簡單的方法來分析記憶體洩漏。 不要驚訝,如果你發現自己遇到類似的情況。 這是很常見的 Windows 存儲應用程式由訂閱到長壽事件源和未能從他們 ; 取消訂閱造成記憶體洩漏 幸運的是,洩露的物件鏈中將清楚地標識問題。 這也包括循環參考中的事件處理常式的情況下,跨語言,以及傳統的 C# / 視覺基本記憶體洩漏引起無限的資料結構進行緩存。

在更複雜的情況下,包含 C#、 Visual Basic、 JavaScript 和 c + + 應用程式中的物件之間的週期的可能導致記憶體洩漏。 這些案件很難進行分析,因為許多物件引用樹中的將顯示作為外部代碼到託管代碼。

Windows 存儲應用程式同時使用注意事項 JavaScript 和 C# 或 Visual Basic

對於在 JavaScript 中生成和使用 C# 或 Visual Basic 來實現基礎元件的應用程式,它是重要的是要記住將兩個單獨的 Gc 管理兩個單獨堆。 這自然會增加應用程式使用的記憶體。 但是,在應用程式的記憶體消耗中最重要的因素將繼續您的大型資料結構和生命週期的管理。 這樣做的跨語言意味著您需要牢記以下:

衡量延遲清理影響垃圾回收堆通常包含等待下一個 GC 的收藏物件。 您可以使用此資訊來調查您的應用程式的記憶體使用。 如果你衡量在之前的記憶體使用方式的差異和手動誘導的垃圾回收之後, 您可以看到多少記憶體正在等待"延遲清理"與"活"物件所使用的記憶體。

雙 GC 的應用程式,瞭解這個三角洲是非常重要的。 由於堆之間的引用,則可能需要垃圾回收,消除可回收的所有物件的序列。 要測試這種效果並清除您 TWS 這樣只活物件保留,則應促使重複,在測試代碼中的交替的 Gc。 您可以觸發回應按鈕按一下 GC (例如),或通過使用性能分析工具支援它。 要觸發 GC 在 JavaScript 中的,請使用以下代碼:

window.CollectGarbage();
For the CLR, use the following:
GC.Collect(2, GCCollectionMode.Optimized);
GC.WaitForPendingFinalizers();

您可能已經注意到對於 CLR 我們使用只有一個 GC。收集與不同的誘導 GC 上診斷記憶體洩漏部分中的代碼的調用。 這是因為在此實例中,我們想要類比將發行,一次只能有一個 GC 的應用程式中的實際 GC 模式,而以前我們想試著盡可能清理盡可能多的物件。 請注意不應該使用 PerfView 部隊 GC 功能來衡量延遲的清理,因為它可能會迫使 JavaScript 和 CLR GC。

應使用相同的技術,來衡量你的記憶體使用中止問題。 在 C#-或 JavaScript-唯一環境、 語言的 GC 將運行自動中止。 但是,在 C# 或 Visual Basic 和 JavaScript 混合應用程式中,只有 JavaScript GC 將運行。 這可能會增加應用程式的私人工作集 (PWS),同時暫停在 CLR 堆上留下一些可回收物品。 根據這些專案有多大,而不是被停止就可以提前終止您的應用程式 (請參閱"避免舉行大型引用關於中止"一節前一條)。

如果對您 PWS 的影響是非常大的它可能值得調用 CLR GC 暫停處理常式中。 但是,這並不應做無測量記憶體消耗大幅度減少,因為一般來說,您想要保留上做工作暫停至最低 (和特別是由系統強制執行遠 5 的第二個時間限制)。

分析兩堆時調查記憶體消耗和消除任何影響延遲清理後,是重要的是要分析的 JavaScript 堆和.NET 堆。 為.NET 堆中,建議的方法是使用 PerfView 工具,"分析記憶體洩漏在 Windows 存儲應用程式使用 C# 和 Visual Basic"部分中,您是否瞭解總記憶體消耗或調查洩漏所述。

PerfView 的當前版本中,你可以看看 JavaScript 的聯合視圖和.NET 堆,使您能夠看到所有物件跨託管語言和瞭解它們之間的任何引用。

Chipalo Street 是在 Windows 8 XAML 團隊的專案經理。他開始工作上 Windows 演示文稿基金會 (WPF) 直接從大學畢業。五年後,他説明了通過三個產品 (WPF、 Silverlight 和 Windows 8 XAML) 而演進的 XAML 和多個版本。在 Windows 8 的發展,期間他擁有的一切相關的文本、 印刷和 XAML 平臺的性能。

Dan Taylor 是在 Microsoft.NET 框架團隊的專案經理。在過去的一年中一直對性能的.NET 框架和 CLR 為 Windows 8 Windows Phone 8 為核心 CLR 泰勒。

由於下面的技術專家對本文的審閱:Deon Brewis、 Mike Hillberg、 戴夫希尼克和伊萬國政