触发Mono中的垃圾收集

本文关键字:Mono 触发 | 更新日期: 2023-09-27 18:19:43

如何让Mono中的垃圾收集器做一些有用的事情?这篇文章的底部是一个简单的C#测试程序,它生成两个大字符串。生成第一个字符串后,变量被取消引用,作用域被退出,垃圾收集器被手动触发。尽管如此,所使用的内存并没有减少,并且在构造第二个字符串期间,程序会爆发内存不足异常。

未处理的异常:内存不足异常[ERROR]致命未处理异常:System.OutOfMemoryException:(包装器)内存不足托管到本机)字符串:位于的InternalAllocateStr(int)System.String.Concat(System.String str0,System.String str1)[0x00000]在GCtest.Main(System.String[]args)[0x0000]在:0 中

经过研究,我发现Mono的--gc=sgen开关使用了不同的垃圾收集算法。更糟糕的是,生成以下堆栈跟踪:

Stacktrace:

at(包装器托管到本机)字符串。InternalAllocateStr(int)<字符串处的0xffffffff>。Concat(字符串,字符串)<0x0005b>在GCtest.Main(string[])<0x00243>在(包装器运行时调用).runtime_invoke_void_object(对象,intptr,intptl,intptr)<0xffffffff>

本地堆栈:

0 mono-sgen 0x000bc086mono_handle_native_sigsegov+422 1 mono-sgen
0x0000466e mono_sigsev_signal_handler+334 2 libsystem_c.dylib
0x913c659b _sigtrap+43
0xffffffff 0x0+44294967295 4单声道sgen
0x0020306d mono_gc_alloc_obj_nolock+363 5 mono-sgen
0x0020394a mono_gc_alloc_string+1536 mono-sgen
0x001c9a10 mono_string_new_size+147 7 mono-sgen
0x0022a6d1 ves_icall_System_String_InternalAllocateStr+28
0x004c450c 0x0+4998412 9
0x004ceec4 0x0+50481860 10
0x004c0f74 0x0+44984692 11
0x004c1163 0x0+4985187 12单声道sgen
0x00010164 mono_jit_runtime_invoke+164 13 mono-sgen
0x001c5791 mono_runtime_invoke+137 14 mono-sgen
0x001c7f92 mono_runtime_exec_main+669 15 mono-sgen
0x001c72cc mono_runtime_run_main+843 16 mono-sgen
0x0008c617 mono_main+8551 17 mono-sgen
0x00002606启动+54 18
0x00000003 0x0+3

来自gdb:的调试信息

/tmp/mono-gdb命令。2aCwlD:1:源命令文件中出错:无法调试自身

在执行本机代码时得到SIGSEGV。这通常表示致命mono运行库或您的应用

/Users/fraser/Documents/dif-match-patch/csharp/GCtest.command:line12:41011中止陷阱:6 mono--gc=sgen GCtest.exe

这是代码:

using System;
public class GCtest {
  public static void Main(string[] args) {
    Console.WriteLine("Memory: " + (GC.GetTotalMemory(true) / 1024) + " KB");
    {
      // Generate the first string.
      string text1 = "hello old world.";
      for (int i = 0; i < 25; i++) {
        text1 = text1 + text1;
      }
      // Dereference variable.
      text1 = null;
      // Drop out of scope.
    }
    GC.Collect();
    GC.WaitForPendingFinalizers();
    Console.WriteLine("Memory: " + (GC.GetTotalMemory(true) / 1024) + " KB");
    // Generate the second string.
    string text2 = "HELLO NEW WORLD!";
    for (int i = 0; i < 25; i++) {
      text2 = text2 + text2;
    }
    Console.WriteLine("Memory: " + (GC.GetTotalMemory(true) / 1024) + " KB");
  }
}

触发Mono中的垃圾收集

这是32位单声道吗?我相信你所描述的行为是由这样一个事实引起的,即对于Boehm GC,至少堆栈是保守扫描的。这意味着它上的值被当作指针对待。如果某个这样的值指向某个对象(或低于该对象),则不会收集该对象。现在很清楚,为什么大对象在这里有问题了——它们可以很容易地填充32位进程的虚拟地址空间,我们很有可能堆栈中的一些值指向其中的某个位置,而整个对象没有被收集。这些令人讨厌的假指针的来源是什么?我最熟悉的是散列、随机值或日期/时间(通常int s较低)。

如何让Mono中的垃圾收集器做一些有用的事情?

您使用的方法是正确的,但我认为您遇到了上述问题。通常的程序不会受到太大的影响,因为(如此)巨大的对象非常罕见。明天我还会在64位单声道上测试它。

然而,自动出现的问题是,为什么Mono项目不制作另一个GC,这不会是保守的?正如您所发现的,这样的垃圾收集器就是sgen。我认为sgen的目的除了是精确的收集器之外,还在于它正在压缩,这对长时间运行的应用程序非常重要,而且它(有时)具有更好的性能。然而,sgen仍处于测试阶段,人们可以观察到这里和那里的崩溃。此外,精确堆栈扫描的功能在不同版本的Mono中被打开和关闭,有时会出现一些回归,所以你可能会发现旧版本的Mono比新版本的效果更好。尽管如此,sgen是积极开发的(可以在github上找到浏览提交历史记录),很快就会取代Mono中默认的垃圾收集器。这将最终解决所描述的问题。

顺便说一句,例如,我的Mono版本(仍然是32位)通过了sgen:的测试

$ mono --gc=sgen GCTest.exe 
Memory: 4098 KB
Memory: 4140 KB
Memory: 1052716 KB

希望这能有所帮助,问问是否有什么不清楚的地方(尤其是由于我的英语水平不高)。

编辑:

在我的64位机器上Boehm运行良好:

$ mono GCTest.exe
Memory: 132 KB
Memory: 280 KB
Memory: 1048860 KB

(自然也是sgen)。它是Linux上的Mono 2.10.5。