【问题标题】:C# Memory ManagmentC# 内存管理
【发布时间】:2018-12-17 05:15:51
【问题描述】:

我很想知道如何在 c# 应用程序中进行内存管理。

即使我处置对象并使其无效,我的应用程序也不会释放内存。 出于测试目的,我创建了如下所述的示例应用程序。

一个应用程序有两个按钮 1)消耗内存 2) 释放内存

通过单击消耗内存,我将创建 500 个内存流对象并将其添加到列表中。

通过单击释放内存,我将处理所有内存流对象并取消该列表。并收集垃圾。

但是当我启动应用程序时,我的任务管理器会显示 8.6 MB 内存使用情况。当我当时按消耗内存时,任务管理器将显示 679.6 MB 内存使用情况。当我按下释放内存时,任务管理器将显示 680.0 MB 内存使用情况。

如何强制释放内存?

代码

public partial class MainWindow : Window
{
    List<System.IO.MemoryStream> MemoryStreamCollection = new List<System.IO.MemoryStream>();

    public MainWindow()
    {
        InitializeComponent();
    }

    private void ConsumeMemory_Click(object sender, RoutedEventArgs e)
    {
        for (int i = 0; i < 500; i++)
        {
            MemoryStreamCollection.Add(new System.IO.MemoryStream(System.IO.File.ReadAllBytes(@"D:\TestPDF.pdf")));
        }

        MessageBox.Show("Done");
    }

    private void ReleaseMemory_Click(object sender, RoutedEventArgs e)
    {
        foreach (System.IO.MemoryStream memoryStream in MemoryStreamCollection)
        {
            memoryStream.Dispose();
        }

        MemoryStreamCollection = null;

        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);

        MessageBox.Show("Done");
    }
}

应用程序启动时的屏幕截图

消耗内存按钮点击后的屏幕截图。

释放内存按钮点击后的屏幕截图。

【问题讨论】:

  • 您是否需要抓住所有物体一次,然后一次性释放它们?如果没有,您可以尝试查看使用 using 块创建它们的行为方式,以便在不再需要它们时自动释放它们。
  • 你试过不带可选参数调用 GC.Collect() 吗?
  • 通过在MemoryStreamCollection = null; 之前添加MemoryStreamCollection.Clear(); 将不再发生内存泄漏。我已经用你的示例代码成功地测试了它。但是我不知道为什么当你强行调用 GC 时这个 List 没有被收集。即使使用WaitForPendingFinalizers,它也不会被收集。
  • 扩展@bommelding 评论、MemoryStream.Dispose()MemoryStream.Close() 真的不需要调用。它们导致相同的方法,并且没有任何非托管资源。你可以阅读更多here

标签: c# wpf memory memory-management memory-leaks


【解决方案1】:

你为什么要这样做?垃圾收集器会在需要时收集垃圾。尝试在 C# 中手动管理内存通常不是一个好主意。

也就是说,您可以尝试使用以下我在运行我不希望被 GC 中断的基准时使用的小技巧(清理从上次基准中释放的所有内存)--

GC.Collect();  

GC.WaitForPendingFinalizers(); 

GC.Collect(); 

GC.WaitForPendingFinalizers();

【讨论】:

    【解决方案2】:

    不建议显式调用gc,但如果调用

    System.GC.Collect();
    System.GC.WaitForPendingFinalizers();
    

    它将在您的代码中显式调用 GC,不要忘记调用 GC.WaitForPendingFinalizers();在 GC.Collect() 之后。

    WaitForPendingFinalizers 并不总是提供更好的性能,它只是阻塞,直到终结队列中的所有对象都完成。如果您希望收集这些对象,则需要再次调用 System.GC.Collect。

    【讨论】:

      【解决方案3】:

      当您添加第三个名为GarbageCollect 的按钮并按如下方式修改您的代码时,它将起作用:

      using System;
      using System.Collections.Generic;
      using System.Windows;
      
      namespace WpfApplication9
      {
          public partial class MainWindow : Window
          {
              List<System.IO.MemoryStream> MemoryStreamCollection = new List<System.IO.MemoryStream>();
      
              public MainWindow()
              {
                  InitializeComponent();
              }
      
              private void ConsumeMemory_Click(object sender, RoutedEventArgs e)
              {
                  for (int i = 0; i < 500; i++)
                  {
                      MemoryStreamCollection.Add(new System.IO.MemoryStream(System.IO.File.ReadAllBytes(@"C:\temp\bpmn.png")));
                  }
      
                  MessageBox.Show("Done");
              }
      
              private void ReleaseMemory_Click(object sender, RoutedEventArgs e)
              {
                  foreach (System.IO.MemoryStream memoryStream in MemoryStreamCollection)
                  {
                      memoryStream.Dispose();
                  }
      
                  MemoryStreamCollection = null;
      
                  MessageBox.Show("Done");
              }
      
              private void GarbageCollect_Click(object sender, RoutedEventArgs e)
              {
                  GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
      
                  MessageBox.Show("Done");
              }
          }
      }
      

      原因是在您的代码中,您正在调用GC.Collect,而foreach 循环的临时变量仍在堆栈中:

      private void ReleaseMemory_Click(object sender, RoutedEventArgs e)
      {
          foreach (System.IO.MemoryStream memoryStream in MemoryStreamCollection)
          {
              memoryStream.Dispose();
          }
      
          MemoryStreamCollection = null;
      
          GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
      
          MessageBox.Show("Done");
      }
      

      通过将GC.Collect 移出函数范围,List 及其迭代器将不再位于本地堆栈上,GC 可以成功完成其工作。

      这是您的示例代码中ReleaseMemory_Click() 方法中MessageBox.Show("Done") 行之前的情况,如内存分析器中所示:

      如您所见,List 和一个 MemoryStream 由ReleaseMemory_Click() 方法的堆栈帧保存。

      【讨论】:

      • 谢谢,@FrankM 它可以工作,但在任何 c# 应用程序中我需要注意避免这种故障。
      • 我的建议:测试、测试、测试...尤其是对于大型数据集。如果遇到任何内存泄漏,请使用 Visual Studio 中的内存分析器或第三方工具来诊断泄漏的根本原因。对于带有IDisposable 接口的类型,尽可能尝试使用using (...) 模式。如果你是 WPF 程序员,请仔细阅读Finding Memory Leaks in WPF;这篇文章已经“救了我的命”十几次。
      猜你喜欢
      • 2010-09-06
      • 2014-10-24
      • 2020-01-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多