この記事は機械翻訳されたものです。

C#、Visual Basic、および C++

Windows ストア アプリでメモリを管理する: 第 2 部 (機械翻訳)

Chipalo Street
Dan Taylor

 

MSDN マガジンは、このシリーズの最初の記事の特別版を説明どのようにメモリ リークが発生する Windows 8 では、なぜ彼らはあなたのアプリケーションのダウンを遅く、全体的なシステムの操作性が低下、リークを回避する一般的な方法、および特定の問題を発見されている 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、対照的に、ガベージ コレクションを使用しません。 代わりに、メモリ管理を実行するには、参照カウントに依存しています。 これは、彼らゼロのアクティブな参照がオブジェクト システムによって取り戻されることを意味します。 これらの言語では、これらのオブジェクト間のサイクルを強制して彼らは決してゼロ参照を持っているだろうので、永遠に生きる 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();
  }
  ...
}

ここで我々 だけボタンと TextBlock をページに追加しました。 ページ クラスは、ボタンの Click イベント用に定義されたイベント ハンドラーも設定しました。 このハンドラーは、ボタンがクリックされるたびに現在の時間を表示する TextBlock 内のテキストを更新します。 あなたが見るように、この単純な例も循環参照があります。

ボタンと TextBlock の子ページでありしたがって、ページへの参照を上の図で示すようが必要図 3

A Circular Reference Related to the Event Handler
図 3 循環参照をイベント ハンドラーに関連します。

下の図では、Page クラスで定義されているイベント ハンドラーの登録によって別の参照が作成されます。

イベントが発生したとき、ソース イベントのハンドラーを呼び出すことができますので、イベント ソース (ボタン) イベント ハンドラーは、デリゲートのメソッドへの強い参照があります。 それからの参照が強いので強いデリゲートこのデリゲートと呼びましょう。

我々 は今、循環参照があります。 ユーザーがページから移動した後、ガベージ コレクター (GC) は、ページとボタンの間のサイクルを再利用するのに十分なスマートです。 JavaScript、c# または Visual Basic でアプリケーションを記述している場合彼らは、もはや必要なときこれらのタイプの循環参照を自動的にクリーンアップするでしょう。 、しかし、C + 前述のとおり c++/cli CX のみの参照カウントがゼロになると、オブジェクトは自動的に削除を意味する、ref カウント言語であります。 ここでは、作成された強い参照ページを強制すると、彼らは決してゼロ参照を持っているだろうので、永遠に生きるボタンをカウントします。 ページはすべてこれらのオブジェクトへの参照を保持するためさらに悪いことに、ページによって (可能性のある非常に大きな要素ツリー) に含まれるアイテムのすべての永遠にも住みたいです。

もちろん、イベントのハンドラーを作成するは非常に一般的なシナリオであるし、マイクロソフトは、これであなたのアプリケーションの使用言語に関係なくリークを引き起こすことを望んでいません。 そのため理由 XAML コンパイラ デリゲートからイベント リスナーへの参照は弱い参照になります。 デリゲートから参照は弱い参照であるためこの弱いデリゲートとして考えることができます。

弱いデリゲート ページ デリゲートからページへの参照によって生きているではないが保証します。 弱い参照ページの参照カウントに対してカウントされません、したがってそれ他のすべての参照がゼロに低下後に破棄するようにします。 その後、ボタン、TextBlock と何か他ページによって参照されても破棄されます。

長寿命のイベント ソース

時々 は長い寿命を持つオブジェクトはイベントを定義します。 それらを定義するオブジェクトの有効期間イベントを共有するため長寿命としてこれらのイベントを指します。 これらの寿命の長いイベントはすべての登録済みハンドラーへの参照を保持します。 これは、ハンドラーと長寿命のイベント ソースとして長い間生き続けるために、ハンドラーの対象となるオブジェクトを強制します。

メモリ リークの以前の記事の「イベント ハンドラー」のセクションでは、この 1 つの例を分析しました。 アプリケーション内の各ページのアプリケーション ウィンドウの SizeChangedEvent を登録します。 アプリのウィンドウの周りにある限り、ウィンドウの SizeChangedEvent からページのイベント ハンドラーへの参照、ページの各インスタンスは生き続けます。 すべてのそれらの 1 つの生きているにもかかわらずだけのままに移動したときのページのビューで。 このリークは、簡単にユーザーがページから移動したとき各ページ 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 (下の図で示すように) の間に配置 ShortLivedObject の代わりに流出する IntermediateObject できます。 IntermediateObject ShortLivedObject の大規模なデータ構造や複雑なビジュアル ツリーが 1 つだけの関数を公開する必要があるため、多くの小さなリークです。

弱いデリゲートのコードに実装する方法を見てをみましょう。 多くのクラスを登録することができますイベントです表示­Properties.OrientationChanged。 OrientationChanged イベント周り永遠になるので画面のプロパティは静的クラスでは実際にです。 イベントは、イベントに耳を傾けるを使用して各オブジェクトへの参照を保持します。 描かれている例では図 5図 6、クラス LargeClass 弱いデリゲート パターン OrientationChanged イベントのイベント ハンドラーを登録すると中間クラスのみが強い参照が保持されるようにします。 中級クラス、OrientationChanged イベントが起動されたときに必要な作業を実際には LargeClass 上で定義されたメソッドを呼び出します。

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

ラムダ

多くの人々 のラムダ式をイベント ハンドラーを実装するが楽 — またはインライン関数 — メソッドではなく。 例から変換してみましょう図 2 まさにそれをする (参照してください図 7)。

図 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();
    }
  ...
}

ラムダ式を使用しても、サイクルを作成します。 最初の参照はまだ明らかに、ボタンと TextBlock のページから作成されます (上の図のような図 3)。

次に示す参照のセット図 8、目に見えて、ラムダによって作成されます。 ボタンの Click イベントは、Invoke メソッドが、コンパイラによって作成された内部オブジェクトの閉鎖によって実装されている RoutedEventHandler オブジェクトにフックされます。 クロージャ ラムダによって参照されるすべての変数への参照を含める必要があります。 これらの変数の 1 つは「この」は — ラムダのコンテキストで — したがって、サイクル作成のページを指します。

References Created by the Lambda
ラムダによって作成された図 8 参照

ラムダは、c# または Visual Basic で書かれた場合、CLR GC このサイクルに関連するリソースが解放されます。 しかしで C + + CX この種類の参照への強い参照をあり、リークが発生します。 これはそれを意味しないすべてラムダで C + +/CX のリーク。 私たちは「この」を参照していなかった場合、ラムダを定義するときにのみ閉鎖にローカル変数を使用循環参照が作成されます。 インライン イベント ハンドラーでの閉鎖の外部の変数にアクセスする必要がある場合は、この問題を解決としてはイベント ハンドラーをメソッドとして代わりに実装します。 これは、イベント ハンドラーにイベントから弱い参照を作成するには、XAML コンパイラと、メモリが解放されます。 別のオプションは強いか、弱い参照 (この場合は、ページ) のポインター メンバー メソッドを含むクラスに対してであるかどうかを指定することができますメンバーへのポインターの構文を使用することです。

イベントの Sender パラメーターを使用します。

説明したように、 の以前の記事、各イベント ハンドラーは通常、イベントのソースを表す"sender"というパラメーターを受信します。 イベント ソースのパラメーターのラムダ式は、循環参照を回避できます。 私たちの例を変更してみましょう (C + を使用して c++/cli 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();
   });
   ...
}

更新のラムダの示す同じ循環参照を作成します図 8。 彼らは C + なります c++/cli myButton「この」の変数を参照する代わりに、ソース パラメーターを使用して、CX にリークではなく、これを避けることが。 閉鎖メソッドが実行されると、「ソース」と"e"パラメーターをスタックに作成されます。 ラムダは、ボタンのイベント ハンドラーに接続されている限り、これらの変数の代わりにメソッド呼び出しの期間だけのライブ (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++ を使用して JavaScript、c# は、C + に加えて、Windows ストア アプリケーションを作成する c++/cli CX および Visual Basic。 こうと、オブジェクトの有効期間を管理するために数えることと、操作の成功または失敗したかどうかを決定する HRESULT 値をテストのリファレンスなどおなじみの COM 技術採用されています。 Windows ランタイム C++ テンプレート ライブラリ (WRL) このコードを記述するプロセスを簡略化 (bit.ly/P1rZrd)。 それ標準 C++ Windows ストア アプリケーションを実装する場合任意のバグを特定して解決するのには非常に難しいことができます、メモリ リークを削減することをお勧めします。

注意言語の境界を越えてイベント ハンドラーを使用します。

最後に、特別な注意が必要です 1 つのコーディング パターンです。 我々 は、イベント ハンドラーを含む、多くの場合は検出およびプラットフォーム提供される緩和策を回避できる循環参照からのリークの可能性を説明しました。 複数のガベージ コレクション ヒープのサイクルを越えるときは、これらの緩和策は適用されません。

示すようにどのようにこれが起こる可能性があります、見てみましょう図 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 イベントのイベント ハンドラーを登録解除できませんでした。 イベント ハンドラーで作成した参照を削除し、すべてのオブジェクトが破壊を得るでしょう。 ラムダの登録を解除することはできませんのでラムダ式をイベント処理は、リークが発生することができることに注意してください。

C# および Visual Basic を使用して Windows ストア アプリケーションでのメモリ リークを分析

C# または Visual Basic でウィンドウ店アプリケーションを書いているなら、テクニックの多くの議論することに注意してくださいするのに便利です、 の以前の記事 JavaScript を c# と Visual Basic にも適用。 特に、弱い参照の使用メモリの増加を減らすために一般的で効果的な方法です (を参照してください bit.ly/S9gVZW 詳細については) と同様、「廃棄」と「肥大化」アーキテクチャ パターンを適用。

今、let's を見つけるし、一般的な修正方法を見てのリークを使用して取る今日利用可能なツール: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、収集の後続の呼び出しの結果としてファイナライザーが解放する任意のオブジェクトも収集を得ることを確認します。

サンプル アプリケーションでは、ただし、ワーキング セットの 108 MB ののみ 7 MB を解放 10 の反復処理後このボタンをクリックします。 この時点で我々 はかなり LeakyApp でメモリ リークがあることを確認することができます。 今、我々 はマネージ コードでのメモリ リークの原因を探す必要があります。

メモリの増加を分析します。

今、CLR GC ヒープの差分を取るに PerfView を使用し、漏らされたオブジェクトを見つけるに diff を分析します。

メモリがリークされている場所見つけるには前に、と後のリーク - を通じて実行のヒープのスナップショットを取るにしたいよ­あなたのアプリケーションでの行動を引き起こしています。 PerfView を使用して、2 つの差分スナップショットをメモリの増加はどこを見つけることができます。

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 で

メモリ リークを示す 2 つのヒープのスナップショットの差分を取得するには。

  1. あなたのアプリケーションでメモリ リークを引き起こすアクションのいくつかの反復を実行します。 これは、ベースラインに遅延ロードまたは 1 回だけ初期化されたオブジェクトが含まれます。
  2. 強制的に任意のデッド オブジェクトを削除するには、GC ヒープのスナップショットを取る。 我々 はこれに「前」に、スナップショットとして参照します。
  3. を介していくつかのあなたのリーク - 繰り返し実行­行動を引き起こしています。
  4. 任意のデッド オブジェクトを削除するには GC を強制を含む別のヒープのスナップショットを取る。 これは、「後」スナップショットになります。
  5. 後のスナップショットのビューからは、Diff のメニュー項目をクリックし、選択、スナップショット、ベースラインとしての前に。 開いたあなたのビューを持っていることを確認する前にスナップショット、またはそれは、diff のメニューに表示されません。
  6. 相違を含む新しいウィンドウが表示されます。 フォールド % と FoldPats テキスト ボックスの内容を削除し、ビューを更新します。

あなたは今、マネージ オブジェクトをマネージ ヒープの 2 つのスナップショット間で、成長を表示するビューがあります。 我々 は LeakyApp を取った、スナップショットの 3 つのイテレーション後と 13 のイテレーションの後の後のスナップショットの前に、GC ヒープの違い後の 10 の反復処理を与えます。 PerfView からのヒープのスナップショット差分が表示されます図 18

The Diff of Two Snapshots in PerfView
図 18 PerfView の 2 つのスナップショットの相違点

Exc 列各型はマネージ ヒープの合計サイズの増加を与えます。 ただし、Exc Ct 列は 2 つの違いではなく、2 つのヒープのスナップショット内のインスタンスの合計が表示されます。 これは何あなたのこの種の分析は、期待していないあり、PerfView の将来のバージョンの差分としてこの列を表示できます。 今のところ、ちょうど相違ビューを使用するとき Exc Ct 列を無視します。

2 つのスナップショット間で流出した任意の型は正の値 Exc 列がオブジェクトのオブジェクト収集から妨げている決定は、いくつかの分析を取る。

差分を分析します。

アプリのあなたの知識に基づいて、差分内のオブジェクトのリストを見て、時間をかけて成長を期待していない任意の種類を見つける必要があります。 あなたのアプリケーションで最初に定義されている型では、リークが参照を上に、アプリケーション コードによって開催されての結果である可能性があるために見てください。 これらは上になく、アプリケーション コードによって開催される可能性があると見て次の場所で流出したタイプ、Windows.UI.Xaml 名前空間です。 もし我々 は我々 のアプリでのみ定義されている型で最初見て、ItemDetailPage タイプ、リストの上部に表示されます。 それは私たちの例のアプリケーションで定義されている最大の流出のオブジェクトです。

リスト内の型をダブルクリックすると取るには"と呼ばれる-から"ビューその型 (sic)。 その型への参照を保持するすべての種類の参照ツリーが表示されます。 すべてのタイプが生きて維持している参照を通じてステップにツリーを展開することができます。 ツリーでは、オブジェクト参照からマネージ コードの外で生きて保管されていること [反時計回り (ObjectType)] の値を意味します (XAML フレームワークなど C++ または JavaScript コード)。 図 19 参照ツリー私たち容疑者の ItemDetailPage オブジェクトのスクリーン ショットを示しています。

The Reference Tree for the ItemDetailPage Type in PerfView
図 19 参照ツリーの ItemDetailPage 型の PerfView

このビューから、ItemDetailPage ライブ、WindowSizeChanged イベントのイベント ハンドラーによって開催されている、これは最も可能性の高いメモリ リークの原因であるはっきり見ることができます。 イベント ハンドラーをマネージ コードでは、この場合は外の何かによって XAML フレームワーク開催されています。 XAML のオブジェクトの 1 つで見れば、彼らはまた、同じイベント ハンドラーによって生き続けているしていることがわかります。 例として、参照ツリーの Windows.UI.Xaml.Controls.Button タイプで表示されます図 20

The Reference Tree for the Windows.UI.Xaml.Controls.Button Type
図 20 参照ツリーの Windows.UI.Xaml.Controls.Button タイプ

このビューからは、あなたが見ることができるすべての UI の新しいインスタンス。Xaml.Controls.Button 保管されている生きているによって ItemDetailPage は、順番に WindowSizeChangedEventHandler で生きて保管されています。

それは私たちに ItemDetailPage、SizeChanged イベント ハンドラーから参照を削除する必要があります、メモリ リークを修正するには、この時点でかなりはっきりですましょう。

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

このオーバーライドを ItemDetailPage クラスに追加した後、ItemDetailPage のインスタンスは、もはや時間をかけて蓄積し、私たちのリークを修正します。

ここで説明する方法メモリ リーク分析するいくつかの簡単な方法を与えます。 似たような状況が発生した場合に驚くことはありません。 Windows ストアのアプリに長命のイベント ソースをサブスクライブと彼らから、解除に失敗によって引き起こされるとメモリ リークは非常に一般的です。 幸いにも、リーク オブジェクトのチェーンは、明確に問題を識別します。 これも、イベント ハンドラー内の循環参照の場合をカバーの言語と同様に伝統的な c#/Visual 基本的なメモリ リーク キャッシュ境界のないデータ構造体によって引き起こされます。

複雑なケースでは、c#、Visual Basic、JavaScript や C++ を含むアプリ内のオブジェクト間のサイクルによってメモリ リークを発生します。 これらのケースは多くのオブジェクト参照ツリーが表示されますのでを分析するは難しいことができますをマネージ コードに外部。

両方を使用する Windows の店の Apps の考慮事項 JavaScript と c# または Visual Basic

JavaScript で構築され、c# または Visual Basic を使用して、基になるコンポーネントを実装するアプリケーションの場合は、2 つの独立したヒープを管理する 2 つの別々 の Gc があることを覚えていることが重要です。 これは当然のことながら、アプリケーションによって使用されるメモリを増加します。 ただし、あなたのアプリケーションのメモリ消費量の最も重要な要因は、大規模なデータ構造とその有効期間を管理すること続けるでしょう。 そう言語間で次の点に注意する必要があることを意味します。

クリーンアップ遅延の影響の測定ガベージ コレクション ヒープには、通常、次の GC を待って収集できるオブジェクトが含まれます。 この情報には、あなたのアプリケーションのメモリ使用を調査できます。 前に、のメモリ使用状況の違いを測定し、手動で誘導されるガベージ コレクションの後、あなたが見ることができる場合どのくらいのメモリ「クリーンアップ遅延の」対「ライブ」オブジェクトによって使用されるメモリ待っていた。

デュアル GC アプリケーションでは、このデルタを理解は非常に重要です。 ヒープ間の参照のため、それはコレクターのすべてのオブジェクトを排除するガベージ コレクションのシーケンスをかかるかもしれない。 この効果をテストするには、ライブ オブジェクトだけが残るように、トーキョーワンダー サイトをオフにあなた繰り返し、Gc、テスト コードで交互に誘導する必要があります。 (たとえば)、ボタンのクリックに応答しての GC をトリガーすることができます。 またはパフォーマンス分析ツールを使用してそれをサポートします。 JavaScript での GC をトリガーするには、次のコードを使用します。

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

あなたは、CLR を 1 つだけの GC を使用することを気づいているかもしれません。GC メモリ リークの診断に関するセクションに誘導するためのコードとは異なり、コールを収集します。 このインスタンスでは以前、我々 しようとすると、多くのオブジェクトを可能な限りクリーンアップしたいとは、一度に 1 つだけの GC を発行するアプリケーションの実際の GC パターンをシミュレートしたいためにです。 それは JavaScript と CLR GC 力がありますので PerfView GC を強制機能遅延クリーンアップを測定するために使用べきではないに注意してください。

同じ手法を使用してメモリの使用を測定することべきで中断します。 C# または JavaScript の言語の GC を自動的に実行する唯一の環境を中断します。 ただし、c# または Visual Basic や JavaScript のハイブリッド アプリケーションでは、JavaScript の GC のみが実行されます。 これはあなたのアプリケーションのプライベート ワーキング セット (PWS) 中断中に増加する CLR ヒープにいくつかコレクターズ アイテムを残すかもしれない。 これらの項目がどのように大きなかに応じて、あなたのアプリケーションが途中で中断なく終了できませんでした (「を避けるため保持大への参照を中断」を参照して、 の以前の記事)。

あなたの PWS への影響が非常に大きい場合は、それは CLR GC の中断ハンドラーを呼び出す価値があるかもしれません。 しかし、これのどれも上の仕事を維持するために一般的にメモリ消費量の大幅削減を測定の中断を最小限に (と特に、どこにも近く 5 番目時間の制限をシステムによって実行することがなく) 行う必要があります。

両方のヒープを分析するメモリ消費量を調査するとき、遅延のクリーンアップのすべての影響を排除した後、それは JavaScript のヒープおよび .NET のヒープを分析することが重要です。 .NET ヒープでは、推奨のアプローチは、"分析メモリ リークの Windows ストアのアプリを使用して c# と Visual Basic"セクションでは、総メモリ消費量を理解するまたは、リークを調査するかどうかを説明する PerfView ツールを使用します。

PerfView の現在のリリースは、あなたは、JavaScript の統合ビューで見ることができるしている、.NET がヒープ、することができのすべてのオブジェクトを参照してくださいマネージ言語間でそれらの間のすべての参照を理解します。

Chipalo Street Windows 8 の XAML チームのプログラム マネージャーです。 彼は大学を出てまっすぐ Windows プレゼンテーション Foundation (WPF) で作業を開始しました。5 年後に彼は XAML (WPF、Silverlight と Windows 8 の XAML) を通して 3 つの製品が進化するに役立っている、複数を解放します。Windows 8 の開発中に、彼はすべてテキスト、印刷および XAML のプラットフォームのパフォーマンスに関連する所有。

Dan Taylor は Microsoft .NET Framework チームのプログラム マネージャーです。 過去 1 年間で、テイラーは .NET Framework および Windows 8 の CLR とコア CLR の Windows Phone 8 のパフォーマンスに取り組んでいます。

この記事のレビュー、次技術専門家のおかげで。ディオン グレッグドーセット マイク ヒルバーグ、デイブ Hiniker、イワン ナランホ