【问题标题】:Is this object recollected by the gargabe collector in this case?在这种情况下,这个对象是由垃圾收集器收集的吗?
【发布时间】:2018-05-27 07:31:32
【问题描述】:

我有一个包含以下代码的 main 方法:

List<MyType> myList = openDialog();

对openDialog的调用打开一个对话框,返回一个带有所选项目的列表,该对话框用于选择项目。

private List<MyType> openDialog()
{
    MyView myView = new MyView();
    MyViewModel myViewModel = new MyViewModel();
    myView.DataContext = myViewModel;
    myView.ShowDialog();

    return myViewModel.Result;        
}

myViewModel.Result 是一个集合,其中包含视图中数据网格的 selectedItems。

我的问题是,我如何返回 ViewModel 的 Result 属性,我不确定 myViewModel 是否会被垃圾收集器重新收集,因为它仍然有对它的引用。

为了避免这种情况,我这样做:

private List<MyType> openDialog()
{
    MyView myView = new MyView();
    MyViewModel myViewModel = new MyViewModel();
    myView.DataContext = myViewModel;
    myView.ShowDialog();

    return new List<MyType>(myViewModel.Result);        
}

在返回时,我正在创建一个新列表以避免引用 Result 属性并确保重新收集 myViewModel 对象,但我想知道是否有办法避免创建新列表。

【问题讨论】:

  • 你有理由相信它没有得到 GC-ed 吗?内存消耗是否一直在增加?
  • 嗯,我的问题是我不知道如何测试对象是否被重新收集。我正在使用 VS 社区,如果它有调试和检查的选项,我不知道如何。或者,也许只有专业版和 avobe 版才有可能。谢谢。
  • 我会从观察进程的内存性能计数器开始(这些计数器随您的 Windows 操作系统提供)。如果您的怀疑仍然存在,请切换到perfview
  • 如果你能提供一个minimal reproducible example 那就太棒了(因为如果不知道MyViewModel Result 到底是什么,就很难给出好的建议)。此外,levibotelho.com/development/how-does-the-garbage-collector-work 可能值得一读。
  • 拿一个内存分析器看看,不要猜。

标签: c# .net wpf mvvm


【解决方案1】:

您还没有发布MyViewModel 的实现,但是从视图模型返回的List&lt;MyType&gt; 不会阻止视图模型被垃圾回收。您可以使用WeakReference 自行确认:

private void Test(object sender, RoutedEventArgs e)
{
    Window myView = new Window();
    MyViewModel myViewModel = new MyViewModel();
    myView.DataContext = myViewModel;
    myView.ShowDialog();

    List<MyType> result = myViewModel.Result;

    WeakReference viewModelWeakReference = new WeakReference(myViewModel);
    myView.DataContext = null;
    myViewModel = null;

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

    bool isViewModelAlive = viewModelWeakReference.IsAlive; //=false
}
...
public class MyViewModel
{
    public List<MyType> Result { get; } = new List<MyType>() { new MyType(), new MyType() };
}

isViewModelAlivefalseresult.Count2 在运行上述示例代码时,即视图模型已被收集但List&lt;MyType&gt; 仍在内存中。

请注意,当您持有对执行 GC 测试的方法的强引用时,WeakReference.IsAlive 将返回 true

List<MyType> _result;
private void Button_Click(object sender, RoutedEventArgs e)
{
    _result = openDialog();
}

private List<MyType> openDialog()
{
    Window myView = new Window();
    MyViewModel myViewModel = new MyViewModel();
    myView.DataContext = myViewModel;
    myView.ShowDialog();

    List<MyType> result = myViewModel.Result;

    WeakReference viewModelWeakReference = new WeakReference(myViewModel);
    myView.DataContext = null;
    myViewModel = null;

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

    bool isViewModelAlive = viewModelWeakReference.IsAlive;

    return result;
}

但是myViewModel 在方法返回后仍然有资格进行垃圾回收,因为它没有 GC 根。

因此无需在此处创建另一个List&lt;MyType&gt;

【讨论】:

    【解决方案2】:

    这个问题有点混乱。不过让我们测试一下。

    给定

    public class MyGCCollectClass
    {
       public List<Version> Garbage { get; set; }
    
       public List<int> MyInnocentList { get; set; }
    
       public List<int> Main()
       {
          Garbage = new List<Version>();
          for (var i = 0; i < 10000000; i++)
          {
             Garbage.Add(new Version());
          }
    
          MyInnocentList = new List<int>();
          return MyInnocentList;
       }
    }
    

    我们可以查看正在收集的内容,而不是查看

    // Lets start from a collect
    GC.Collect();
    GC.WaitForPendingFinalizers();
    Console.WriteLine("Memory used before collection:       {0:N0}", GC.GetTotalMemory(false));
    
    // create a class/viewmodel
    var gcCollectClass = new MyGCCollectClass();
    
    // create a world of garbage
    // return back a list that is part of the class
    var list = gcCollectClass.Main();
    
    // lets see whats GC has
    Console.WriteLine("Memory used: {0:N0}", GC.GetTotalMemory(true));
    
    // make sure our garbage is still alive
    Console.WriteLine(gcCollectClass.Garbage.Count);
    
    // Kill the original class
    gcCollectClass = null;
    
    // Force a collection
    GC.Collect();
    GC.WaitForPendingFinalizers();
    
    // double check the list is still alive
    Console.WriteLine(list.Count);
    
    // Lets make sure we havent caused a memory leak
    Console.WriteLine("Memory used after full collection:   {0:N0}", 
    GC.GetTotalMemory(true));
    

    输出

    Memory used before collection:       30,088
    Memory used:   307,138,940
    10000000
    1
    Memory used after full collection:   29,968
    

    事实是,仅仅因为你从你的类返回一个列表并且它是类的一部分并不意味着返回它会阻止你的视图模式被清理。在这种情况下它“会”被收集起来,并且没有内存泄漏

    进一步阅读

    Why doesn't C# support the return of references?

    Memory in .NET - what goes where

    变量的内存槽存储在堆栈或 堆。这取决于声明它的上下文:

    1. 每个局部变量(即在方法中声明的变量)都存储在堆栈中。这包括引用类型变量 - 变量本身是 在堆栈上,但请记住引用类型变量的值 只是一个引用(或 null),而不是对象本身。方法 参数也算作局部变量,但如果它们被声明为 ref 修饰符,他们没有自己的插槽,但与 调用代码中使用的变量。请参阅我关于参数的文章 通过了解更多详情。
    2. 引用类型的实例变量总是在堆上。这就是对象本身“存在”的地方。
    3. 值类型的实例变量存储在与声明值类型的变量相同的上下文中。内存插槽用于 instance 有效地包含了 实例。这意味着(鉴于前两点)一个结构 在方法中声明的变量将始终在堆栈上,而 作为类的实例字段的结构变量将位于 堆。
    4. 每个静态变量都存储在堆中,无论它是在引用类型还是值类型中声明的。只有 无论创建多少实例,一共 1 个插槽。 (那里 不需要为该插槽创建任何实例 不过。)关于变量存在于哪个堆的详细信息是 复杂,但在 MSDN 文章中详细解释 主题。

    https://ericlippert.com/2011/06/23/ref-returns-and-ref-locals/

    【讨论】:

      【解决方案3】:

      myViewModel变量引用的对象能否被收集,取决于是否有其他可达对象引用它。在这种情况下,假设没有其他人引用它,这取决于 myViewModel.Result 属性返回的对象是否有任何引用(例如字段、属性、委托)。如果没有,则不需要复制集合。如果是这样,副本似乎是一个可以接受的解决方案。

      除非视图模型使用一些有限的资源(数据库连接、文件句柄、网络连接等等,在这种情况下它应该使用 IDisposable 模式),没有理由过度担心它的垃圾收集。

      不过,为了满足大家的好奇心,可以测试一下对象是否被GCed using the answer here

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-07-24
        • 1970-01-01
        • 1970-01-01
        • 2019-01-14
        • 2010-11-10
        • 2023-03-19
        相关资源
        最近更新 更多