在Mono中触发垃圾收集

时间:2012-01-06 20:45:08

标签: c# mono garbage-collection

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

  

未处理的异常:OutOfMemoryException [ERROR]致命的未处理   EXCEPTION:System.OutOfMemoryException:(包装器   托管到本机)字符串:InternalAllocateStr(int)at   System.String.Concat(System.String str0,System.String str1)   [0x00000] in:GCtest.Main为0(System.String []   args)[0x00000] in:0

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

  

堆栈跟踪:

     

at(wrapper managed-to-native)string.InternalAllocateStr(int)   < 0xffffffff的> at string.Concat(string,string)< 0x0005b>在   GCtest.Main(string [])< 0x00243> at(包装器运行时调用)   .runtime_invoke_void_object(object,intptr,intptr,intptr)   < 0xffffffff的>

     

原生堆栈跟踪:

     

0 mono-sgen 0x000bc086   mono_handle_native_sigsegv + 422 1 mono-sgen
  0x0000466e mono_sigsegv_signal_handler + 334 2 libsystem_c.dylib
  0x913c659b _sigtramp + 43 3 ???   0xffffffff 0x0 + 4294967295 4 mono-sgen
  0x0020306d mono_gc_alloc_obj_nolock + 363 5 mono-sgen
  0x0020394a mono_gc_alloc_string + 153 6 mono-sgen
  0x001c9a10 mono_string_new_size + 147 7 mono-sgen
  0x0022a6d1 ves_icall_System_String_InternalAllocateStr + 28 8 ???   0x004c450c 0x0 + 4998412 9 ???   0x004ceec4 0x0 + 5041860 10 ???   0x004c0f74 0x0 + 4984692 11 ???   0x004c1163 0x0 + 4985187 12 mono-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-commands.2aCwlD:1:源命令文件出错:无法   调试自我

     

执行本机代码时获得了SIGSEGV。这通常表示致命   单声道运行时中的错误或您使用的本机库之一   应用

     

/Users/fraser/Documents/diff-match-patch/csharp/GCtest.command:line   12:41011中止陷阱:6个单声道--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");
  }
}

1 个答案:

答案 0 :(得分:8)

这是32位单声道吗?我相信你所描述的行为是由于这一事实造成的,即使用Boehm GC,至少堆栈是经过保守扫描的。这意味着它的值被视为指针。如果某个此类值指向对象(或其下方),则不会收集此对象。现在很清楚,为什么大对象在这里有问题 - 它们可以轻松填充32位进程的虚拟地址空间,我们很有可能堆栈中的某些值指向其中的某个位置并且不收集整个对象。这些令人讨厌的假指针的来源是什么?我最熟悉的是哈希值,随机值或日期/时间(通常int s很低)。

  

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

您使用的方法是正确的,但我认为您遇到了上述问题。通常的程序不会受到太大影响,因为(所以)巨大的物体是非常罕见的。明天我也将在64位Mono上进行测试。

尽管如此,自动出现的问题是为什么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。