Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/22.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 数组中的对象未被垃圾回收_C#_.net_Garbage Collection - Fatal编程技术网

C# 数组中的对象未被垃圾回收

C# 数组中的对象未被垃圾回收,c#,.net,garbage-collection,C#,.net,Garbage Collection,我正在测试一个类,该类使用弱引用来确保对象能够被垃圾收集,我发现即使列表不再被引用,列表中的对象也不会被收集。简单数组也是如此。下面的代码片段显示了一个失败的简单测试 class TestDestructor { public static bool DestructorCalled; ~TestDestructor() { DestructorCalled = true; } } [Test] public void TestGarbageC

我正在测试一个类,该类使用弱引用来确保对象能够被垃圾收集,我发现即使列表不再被引用,列表中的对象也不会被收集。简单数组也是如此。下面的代码片段显示了一个失败的简单测试

class TestDestructor
{
    public static bool DestructorCalled;

    ~TestDestructor()
    {
        DestructorCalled = true;
    }
}

[Test]
public void TestGarbageCollection()
{
    TestDestructor testDestructor = new TestDestructor();

    var array = new object[] { testDestructor };
    array = null;

    testDestructor = null;

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

    Assert.IsTrue(TestDestructor.DestructorCalled);
}
忽略数组的初始化将导致测试通过


为什么数组中的对象没有被垃圾收集?

如果我没有弄错的话,那是因为当加载到数组中时,对象基本上是被复制的,并且与最初创建的对象分离。然后,销毁阵列和原始对象时,复制到阵列的对象仍然存在


垃圾收集最终应该完成它的工作,但我知道你们试图强迫它清除资源。我要尝试的是在销毁阵列之前先清除阵列(移除该对象),然后查看它是否清除了所有内容。

编辑:好的,我正在这方面取得一些进展。可能涉及三个二进制开关(至少):

  • 代码是否优化;i、 e.命令行上的
    /o+
    /o-
    标志。这似乎没有什么区别
  • 代码是否在调试器中运行。这似乎没有什么区别
  • 生成的调试信息级别,即
    /debug+
    /debug-
    /debug:full
    /debug:pdbonly
    命令行标志。只有
    /debug+
    /debug:full
    会导致它失败
此外:

  • 如果将
    Main
    代码与
    TestDestructor
    代码分开,可以看出是
    Main
    代码的编译模式造成了差异
  • 据我所知,在方法本身中为
    /debug:pdbonly
    生成的IL与为
    /debug:full
    生成的IL相同,因此它可能是一个清单问题
编辑:好吧,这真的很奇怪。如果我拆解“损坏”版本,然后重新组装,它可以工作:

ildasm /out:broken.il Program.exe
ilasm broken.il
ilasm有三种不同的调试设置:
/debug
/debug=OPT
/debug=IMPL
。使用前两种方法中的任何一种都会失败——使用后一种方法,它会工作。最后一个被描述为启用JIT优化,所以这大概就是这里的区别所在。。。虽然在我看来,它应该仍然能够收集任何方式的对象


这可能是由于
析构函数调用的内存模型造成的。它不是易失性的,因此不能保证来自终结器线程的写入被测试线程“看到”

在这种情况下,当然会调用终结器。在将变量设置为volatile之后,这个独立的等效示例(对我来说运行起来更简单)对我来说肯定是真的。当然,这并不是证据:没有
volatile
代码就不能保证失败;它只是不能保证工作。你能让你的测试在变为易变变量后失败吗

using System;

class TestDestructor
{
    public static volatile bool DestructorCalled;

    ~TestDestructor()
    {
        DestructorCalled = true;
    }
}

class Test
{
    static void Main()
    {
        TestDestructor testDestructor = new TestDestructor();

        var array = new object[] { testDestructor };
        array = null;

        testDestructor = null;

        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine(TestDestructor.DestructorCalled);
    }
}

编辑:我刚刚看到在使用VisualStudio构建时失败了,但是从命令行可以看到。现在查看IL…

另一个编辑:如果数组在Main()方法范围中定义,则结果将始终为false,但如果在类测试范围中定义,则结果将为true。也许这不是一件坏事

class TestDestructor
{
    public TestDestructor()
    {
        testList = new List<string>();
    }

    public static volatile bool DestructorCalled;

    ~TestDestructor()
    {
        DestructorCalled = true;
    }

    public string xy = "test";

    public List<string> testList;

}

class Test
{
    private static object[] myArray;

    static void Main()
    {
        NewMethod();            
        myArray = null;

        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine(TestDestructor.DestructorCalled);
        Console.In.ReadToEnd();
    }

    private static void NewMethod()
    {
        TestDestructor testDestructor = new TestDestructor() { xy = "foo" };
        testDestructor.testList.Add("bar");
        myArray = new object[] { testDestructor };
        Console.WriteLine(myArray.Length);
    }
}
类TestDestructor { 公共TestDestructor() { testList=新列表(); } 已调用公共静态易失性布尔销毁; ~TestDestructor() { 析构函数调用=真; } 公共字符串xy=“test”; 公共列表测试列表; } 课堂测试 { 私有静态对象[]myArray; 静态void Main() { NewMethod(); myArray=null; GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine(TestDestructor.DestructorCalled); Console.In.ReadToEnd(); } 私有静态void NewMethod() { TestDestructor TestDestructor=新的TestDestructor(){xy=“foo”}; testDestructor.testList.Add(“bar”); myArray=新对象[]{testDestructor}; Console.WriteLine(myArray.Length); } }
就是这么说的:

实现Finalize方法或析构函数可能会对性能产生负面影响,您应该避免不必要地使用它们。使用Finalize方法回收对象使用的内存至少需要两次垃圾回收。[…]未来的垃圾收集将确定最终确定的对象确实是垃圾,因为标记为准备完成的对象列表中的条目不再指向它们。在未来的垃圾收集中,对象的内存实际上是回收的


尝试使用一种处理机制,而不是最终确定,以查看将发生什么情况

正如Ani在评论中指出的,整个阵列在发布模式下进行了优化,因此我们应该将代码更改为如下所示:

class TestDestructor
{
    public static bool DestructorCalled;
    ~TestDestructor()
    {
        DestructorCalled = true;
    }
}

class Test
{
    static void Main()
    {
        TestDestructor testDestructor = new TestDestructor();

        var array = new object[] { testDestructor };
        Console.WriteLine(array[0].ToString());
        array = null;

        testDestructor = null;

        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine(TestDestructor.DestructorCalled);
    }
} 

对我来说,它工作(没有挥发性)并且总是打印真实的。是否有人可以确认在发布模式下没有调用终结器,否则我们可以假设它与调试模式相关。

只是猜测,但数组是否“真的”创建了?也许它被优化了?确实,在销毁对象之前,是否可以对其执行某些操作?您还应该尝试在析构函数中放置Console.WriteLine。这与生成有关吗?在垃圾收集上,testDestructor仍然作为数组的一个元素存在(即使数组被取消引用?),因此在这个周期内不会被收集。如果收集两次会发生什么情况?Jon Skeet关于需要
volatile
的说法是正确的,这看起来像是一个
调试
vs
发布
的问题。@Jon:在发布模式下简要检查IL,看起来C编译器完全省略了数组分配。请注意,断言是针对sta的