在Mono中触发垃圾收集

Nei*_*ser 12 c# mono garbage-collection

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

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

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

堆栈跟踪:

at(wrapper managed-to-native)string.InternalAllocateStr(int)<0xffffffff> at string.Concat(string,string)<0x0005b> at GCtest.Main(string [])<0x00243> at at(wrapper runtime-invoke). 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单SGEN
0x00010164 mono_jit_runtime_invoke + 164 13单SGEN
0x001c5791 mono_runtime_invoke + 137 14单SGEN
0x001c7f92 mono_runtime_exec_main + 669 15单SGEN
0x001c72cc mono_runtime_run_main + 843 16单SGEN
0x0008c617 mono_main + 8551 17单SGEN
0x00002606开始+ 54 18 ???
0x00000003 0x0 + 3

来自gdb的调试信息:

/tmp/mono-gdb-commands.2aCwlD:1:源命令文件出错:无法自我调试

执行本机代码时获得SIGSEGV.这通常表示单声道运行时或应用程序使用的本机库之一出现致命错误.

/Users/fraser/Documents/diff-match-patch/csharp/GCtest.command:第12行: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");
  }
}
Run Code Online (Sandbox Code Playgroud)

kon*_*ski 8

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

如何让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
Run Code Online (Sandbox Code Playgroud)

希望这有用,请问是否有些不清楚(特别是由于我的第二个优质英语).

编辑:

在我的64位机器上,Boehm效果很好:

$ mono GCTest.exe
Memory: 132 KB
Memory: 280 KB
Memory: 1048860 KB
Run Code Online (Sandbox Code Playgroud)

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