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、 戴夫希尼克和伊万国政