我们的问题很简单,但原因却很复杂。我们是经验丰富的开发人员,并且对可能导致此问题的原因进行了大量研究。我们希望 MonoTouch 开发人员能够与我们合作,找出人们所遇到的常见问题,以及目前尚无解决方案的问题。我们已经为此工作了两个多星期,但未能解决。
问题是:为什么我们的 MonoTouch 应用程序会破坏垃圾收集器?它并没有失忆。
情况是我们有一个应用程序定期检查 Web 服务(可能每 5 秒一次)。一段时间后,它会因内存管理中止而失败。这通常发生在大约一个半小时后,但也可能持续十分钟到过夜。这种情况发生在我们所有的测试设备上(我们总共有 7 个设备,涵盖 iOS3 和 iOS4、iPod Touch、iPhone 和 iPad(1 和 2)。查看 StackOverflow 后,我们在计时器中添加了一个 System.Gc.Collect,然后再进行任何操作。这稍微改善了一些情况(失败需要更长的时间),但它并没有消失。还值得补充的是,iPad 的内存日志显示有 777 个空闲块,我们的应用程序正在使用 2041 个块,总共有 26488 个有线页面。由于我们已经进行了垃圾收集,并且没有做任何与 5 秒前所做的不同的事情,所以内存不足似乎很奇怪。
我们升级到了 MonoTouch 4.0.1,但这并没有修复它。
StackOverflow 问题可能涉及同一问题,但没有回答:5666905 https://stackoverflow.com/questions/5666905 / 4545383 https://stackoverflow.com/questions/4545383 / 5492469 https://stackoverflow.com/questions/5492469 / 5426733 https://stackoverflow.com/questions/5426733
iPad2 上发生故障的堆栈如下。失败可能发生在主线程或 http 线程中,但总是发生在这个 GC_ 序列中。我在下面包含了内存管理器 GC_remap 的代码,并进行了讨论。
Thread 10 Crashed:
0 libsystem_kernel.dylib 0x34b4da1c __pthread_kill + 8
1 libsystem_c.dylib 0x3646a3b4 pthread_kill + 52
2 libsystem_c.dylib 0x36462bf8 abort + 72
3 MyApp 0x004ca92c mono_handle_native_sigsegv (mini-exceptions.c:2249)
4 MyApp 0x004f2208 sigabrt_signal_handler (mini-posix.c:195)
5 libsystem_c.dylib 0x36475728 _sigtramp + 36
6 libsystem_c.dylib 0x3646a3b4 pthread_kill + 52
7 libsystem_c.dylib 0x36462bf8 abort + 72
8 MyApp 0x0061dc94 GC_remap (os_dep.c:2092)
9 MyApp 0x00611678 GC_allochblk_nth (allchblk.c:730)
10 MyApp 0x00611028 GC_allochblk (allchblk.c:561)
11 MyApp 0x0061d0e0 GC_new_hblk (new_hblk.c:253)
12 MyApp 0x006133d0 GC_allocobj (alloc.c:1116)
13 MyApp 0x00617d30 GC_generic_malloc_inner (malloc.c:136)
14 MyApp 0x00617f40 GC_generic_malloc (malloc.c:192)
15 MyApp 0x00618264 GC_malloc_atomic (malloc.c:262)
16 MyApp 0x005a46d4 mono_object_allocate_ptrfree (object.c:4221)
17 MyApp 0x005a4aa0 mono_string_new_size (object.c:4848)
18 MyApp 0x005c1b14 ves_icall_System_String_InternalAllocateStr (string-icalls.c:213)
19 MyApp 0x002d34c4 wrapper_managed_to_native_string_InternalAllocateStr_int + 52
20 MyApp 0x002cff5c string_ToLower_System_Globalization_CultureInfo + 56
21 MyApp 0x003e6ac0 System_Net_WebRequest_GetCreator_string + 40
22 MyApp 0x003e694c System_Net_WebRequest_Create_System_Uri + 48
23 MyApp 0x003e68d8 System_Net_WebRequest_Create_string + 64
24 MyApp 0x004489c4 MyApp_Services_Client_GetResponseContent_string + 152
25 MyApp 0x00446288 MyApp_Services_Client_GetCurrentQuestion_long_long + 916
26 MyApp 0x00196fcc MyApp_Iphone_RootViewController_RetrieveCurrentQuestion + 868
27 MyApp 0x002e6368 System_Threading_Thread_StartUnsafe + 168
28 MyApp 0x00306890 wrapper_runtime_invoke_object_runtime_invoke_dynamic_intptr_intptr_intptr_intptr + 192
29 MyApp 0x004b0274 mono_jit_runtime_invoke (mini.c:5746)
30 MyApp 0x0059f924 mono_runtime_invoke (object.c:2756)
31 MyApp 0x005a1350 mono_runtime_delegate_invoke (object.c:3421)
32 MyApp 0x005ca884 start_wrapper_internal (threads.c:788)
33 MyApp 0x005ca924 start_wrapper (threads.c:830)
34 MyApp 0x005ef4b8 thread_start_routine (wthreads.c:285)
35 MyApp 0x0061f1d0 GC_start_routine (pthread_support.c:1468)
36 libsystem_c.dylib 0x3646a30a _pthread_start + 242
37 libsystem_c.dylib 0x3646bbb4 thread_start + 0
这是 GC_remap 代码,似乎是失败点,来自https://github.com/mono/mono/blob/master/libgc/os_dep.c https://github.com/mono/mono/blob/master/libgc/os_dep.c
#ifdef NACL
{
/* NaCl doesn't expose mprotect, but mmap should work fine */
void * mmap_result;
mmap_result = mmap(start_addr, len, PROT_READ | PROT_WRITE | OPT_PROT_EXEC,
MAP_PRIVATE | MAP_FIXED | OPT_MAP_ANON,
zero_fd, 0/* offset */);
if (mmap_result != (void *)start_addr) ABORT("mmap as mprotect failed");
/* Fake the return value as if mprotect succeeded. */
result = 0;
}
#else /* NACL */
result = mprotect(start_addr, len,
PROT_READ | PROT_WRITE | OPT_PROT_EXEC);
#endif /* NACL */
if (result != 0) {
GC_err_printf3(
"Mprotect failed at 0x%lx (length %ld) with errno %ld\n",
start_addr, len, errno);
ABORT("Mprotect remapping failed");
}
GC_unmapped_bytes -= len;
看来 ABORT 是由 mprotect 功能失败引起的。我们无法获取故障代码,因为问题并未在模拟器上显现出来。 mprotect 函数似乎只是将内存标记为可读取/写入/执行。内存管理器如何传递导致其失败的参数?它是否传递了错误的指针或错误的长度?或者某些区域或边界在 iOS 上的处理方式有所不同?
代码位于https://github.com/mono/mono/blob/master/libgc/allchblk.c https://github.com/mono/mono/blob/master/libgc/allchblk.cGC_allochblk_nth 意味着仅当找到的内存块有效时才会调用 GC_remap 函数。 (该文件与堆栈跟踪的行号不太匹配,因此推测它不是完全相同的文件。)
http://developer.apple.com/library/ios/#documentation/System/Conceptual/ManPages_iPhoneOS/man2/mprotect.2.html http://developer.apple.com/library/ios/#documentation/System/Conceptual/ManPages_iPhoneOS/man2/mprotect.2.html说它可能会失败, EACCES、EINVAL、ENOTSUP 分别为 13、22 和 45。 SO 的一份报告称他们收到错误 12 (ENOMEM)。我不确定这意味着什么,因为 mprotect 不应该分配内存,并且文档没有说这是有效的。
更通用的文档位于http://linux.die.net/man/2/mprotect http://linux.die.net/man/2/mprotect表示 ENOMEM 可能是由于“无法分配内部内核结构。或者:[addr, addr+len]范围内的地址对于进程的地址空间无效,或者指定了一个或多个未映射的页面。 ”怎么会这样?
我们非常感谢任何有关我们如何推进这一工作的建议。除了 C# 代码之外,我们没有做任何事情,除了定期 https 读取之外,我们没有做任何事情。我们可以做些什么来改进调试(我们无法跟踪任何内容,因为应用程序被 iOS 杀死了)。我们尝试创建一个更简单的演示,但它的失败速度不够快,不值得使用。如果 Novell MonoTouch 开发人员想要我们的源代码,我们可以在明显保密的情况下提供。