MonoTouch不稳定性持续存在:托管内存分配器崩溃

8
长话短说:我可以分配大量的非托管内存,但是尝试在托管内存中分配相同数量(或更少)的内存会导致MonoTouch在GC_remap中崩溃(下面是调用堆栈)。 细节: 我将谈论上述行为的一个例子。我的应用程序偶尔会分配2.5MB的托管内存(使用new byte[]),并且它经常在我的iPhone4上死机,并出现下面粘贴的调用堆栈(即在分配期间出现mprotect错误)。我不会在单个函数调用之外保留对这些2.5MB块的引用。 MonoTouch团队表示,“mprotect errno 12”表示您已经耗尽了设备上的内存, 但问题是,我有大量可用于我的应用程序的内存。我可以在应用程序启动时分配0MB10MB200MB非托管内存(使用Marshal.AllocHGlobal),每帧都触摸它,并且这对我的应用程序行为或此mprotect错误的频率没有任何影响。

一些注意事项

  • GC.TotalMemory告诉我,我的应用程序始终占用 3MB 5MB 的托管内存使用量。
  • 我在应用程序中有其他地方分配了更大的非托管内存块,但它从未在那里崩溃。我创建了压力测试,加载4MB的(非托管)纹理数据,将其交给GL,并每帧绘制它,直到我开始请求大块托管内存为止,应用程序是非常稳定的。
  • 除非我自己调用GC.Collect,否则GC.CollectionCount几乎不会改变。
  • 相同的行为发生在MonoTouch 3.2.3和MonoTouch 4.0上。
  • 相同的行为在所有测试设备上都发生(iPhone 3G、3GS、4、iPad、iPad2)。
  • 相同的行为在发布版本和调试版本中发生,尽管在调试版本中更频繁。

引起崩溃的方法

  • 如果我创建一个循环调用GC.Collect然后睡眠1毫秒的线程,这会使崩溃更快地发生(例如,如果我在调试构建中,则几乎立即发生)。
  • 使用某些.NET功能,如WebRequest,也会导致此崩溃。我只能假设它在其中某个地方分配了大块托管内存。

解决崩溃的方法

有两种方法可以降低崩溃频率或完全修复它:

  1. 如果我先预分配那个2.5MB的托管内存块,然后让它在应用程序的整个生命周期中保持不变,则不会出现崩溃。
  2. 如果在对其进行任何操作之前钉住这个2.5MB的内存块,似乎有所帮助。

结论/问题

由于这个问题,我们的应用程序尚未实现完全稳定。这个崩溃(总是在GC_remap内部)会在我们应用程序的随机分配中发生(我在这里提供的2.5MB示例只是我选择隔离和重现这个问题的一个示例)。

问题:

  1. 我能完全不信任托管分配器吗?
  2. 为什么我可以分配200MB的非托管内存,但是当我请求2.5MB时,托管分配器就会崩溃?(注意:即使我没有分配200MB的非托管内存,当我请求2.5MB时,它也会崩溃)。
  3. 为什么如果我在应用程序的生命周期中一直占用这2.5MB,那么应用程序就完全正常,但是如果我将其归还给系统(并调用GC.Collect),然后稍后再请求另外的2.5MB,崩溃情况会更糟糕!如果这真的是低内存条件,那么将2.5MB归还给系统而不是占用它是否更好?

我们能使用MonoTouch吗?

我的团队正在认真考虑放弃MonoTouch作为我们产品的开发工具,因为我们无法使其保持可靠稳定。

我们无法从MonoTouch团队那里得到任何关于这个问题的回复,无论是在stackoverflow上,还是通过在Novell网站上提交错误报告,或者直接给MonoTouch的支持邮件发送电子邮件。我们已经将我们的(托管和非托管)内存使用降到了荒谬的低水平,但由于这个问题,应用程序仍然会崩溃。
短期内,我唯一想到的解决方法是在启动时分配一个大块内存(2-5MB),将其固定,使垃圾收集器永远不会触及它,并编写自己的分配器来按需分配此内存块的部分给我的应用程序。但如果这是在MonoTouch下可能实现的最佳解决方案,那么我将尽快从MonoTouch中脱身并要求退款。

...

Mprotect failed at 0xaa00000 (length 3801088) with errno 12
Stacktrace:

  at MyApp.GameScreen/VerifyPictureDialog.StoreBasePictureData () [0x00000] in /Users/dussault/s/MyApp/Main/Src/PhotoScreens.cs:428
  at MyApp.GameScreen/VerifyPictureDialog.ApplyFilters (bool) [0x0004b] in /Users/dussault/s/MyApp/Main/Src/PhotoScreens.cs:640
  at MyApp.GameScreen/VerifyPictureDialog.Simulate (single) [0x00077] in /Users/dussault/s/MyApp/Main/Src/PhotoScreens.cs:477
  at MyApp.BaseWindow.Simulate (single) [0x00007] in /Users/dussault/s/MyApp/Main/Src/BaseWindow.cs:56
  at MyApp.BaseWindow.Simulate (single) [0x00007] in /Users/dussault/s/MyApp/Main/Src/BaseWindow.cs:56
  at MyApp.GameScreen.Simulate (single) [0x00238] in /Users/dussault/s/MyApp/Main/Src/GameScreen.cs:3114
  at MyApp.BaseWindow.Simulate (single) [0x00007] in /Users/dussault/s/MyApp/Main/Src/BaseWindow.cs:56
  at MyApp.WindowMgr.Simulate (single) [0x0002f] in /Users/dussault/s/MyApp/Main/Src/WindowMgr.cs:126
  at MyApp.Game1.Update (Microsoft.Xna.Framework.GameTime) [0x0010f] in /Users/dussault/s/MyApp/Main/Src/Game1.cs:1194
  at Microsoft.Xna.Framework.Game.DispatchUpdate (Microsoft.Xna.Framework.GameTime) [0x00000] in /Users/dussault/s/MyApp/Main/Src/XNA-Emulation/GraphicsDevice.cs:531
  at MyApp_iOS.EAGLView.OnUpdateFrame () [0x00050] in /Users/dussault/s/MyApp/Main/Src/iOS/EAGLView.cs:310
  at MyApp_iOS.EAGLView.SimulateAndRender () [0x0000a] in /Users/dussault/s/MyApp/Main/Src/iOS/EAGLView.cs:279
  at MyApp_iOS.EAGLView.MainLoopTimerCallback () [0x00006] in /Users/dussault/s/MyApp/Main/Src/iOS/EAGLView.cs:231
  at MonoTouch.Foundation.NSActionDispatcher.Apply () <0x0002b>
  at (wrapper runtime-invoke) object.runtime_invoke_dynamic (intptr,intptr,intptr,intptr) <0xffffffff>
  at MonoTouch.UIKit.UIApplication.Main (string[],string,string) <0x000cf>
  at MonoTouch.UIKit.UIApplication.Main (string[]) <0x00023>
  at MyApp_iOS.Application.Main (string[]) [0x00000] in /Users/dussault/s/MyApp/Main/Src/iOS/Main.cs:57
  at (wrapper runtime-invoke) object.runtime_invoke_dynamic (intptr,intptr,intptr,intptr) <0xffffffff>

Native stacktrace:

    0   MyApp_iOS                           0x00af1b48 mono_handle_native_sigsegv + 412
    1   MyApp_iOS                           0x00b1c66c sigabrt_signal_handler + 148
    2   libSystem.B.dylib                   0x33bd3ddf _sigtramp + 42
    3   libSystem.B.dylib                   0x33bd52cb kill + 10
    4   libSystem.B.dylib                   0x33bd52bd raise + 16
    5   libSystem.B.dylib                   0x33be9d79 abort + 56
    6   MyApp_iOS                           0x00c74378 GC_remap + 200
    7   MyApp_iOS                           0x00c62c04 GC_allochblk_nth + 1536
    8   MyApp_iOS                           0x00c625b4 GC_allochblk + 96
    9   MyApp_iOS                           0x00c6bf6c GC_alloc_large + 132
    10  MyApp_iOS                           0x00c6c5e8 GC_generic_malloc + 324
    11  MyApp_iOS                           0x00c6c8c8 GC_malloc_atomic + 332
    12  MyApp_iOS                           0x00bd8e88 mono_object_allocate_ptrfree + 64
    13  MyApp_iOS                           0x00bd8ff4 mono_array_new_specific + 148
    14  MyApp_iOS                           0x009173f4 wrapper_managed_to_native_object___icall_wrapper_mono_array_new_specific_intptr_int + 68
    15  MyApp_iOS                           0x002cd880 MyApp_GameScreen_VerifyPictureDialog_ApplyFilters_bool + 628
    16  MyApp_iOS                           0x002cbffc MyApp_GameScreen_VerifyPictureDialog_Simulate_single + 768
    17  MyApp_iOS                           0x002ef9d0 MyApp_BaseWindow_Simulate_single + 280
    18  MyApp_iOS                           0x002ef9d0 MyApp_BaseWindow_Simulate_single + 280
    19  MyApp_iOS                           0x002a71fc MyApp_GameScreen_Simulate_single + 2736
    20  MyApp_iOS                           0x002ef9d0 MyApp_BaseWindow_Simulate_single + 280
    21  MyApp_iOS                           0x0038068c MyApp_WindowMgr_Simulate_single + 376
    22  MyApp_iOS                           0x0027f798 MyApp_Game1_Update_Microsoft_Xna_Framework_GameTime + 1992
    23  MyApp_iOS                           0x0039afc8 Microsoft_Xna_Framework_Game_DispatchUpdate_Microsoft_Xna_Framework_GameTime + 148
    24  MyApp_iOS                           0x0026ec10 MyApp_iOS_EAGLView_OnUpdateFrame + 716
    25  MyApp_iOS                           0x0026e8cc MyApp_iOS_EAGLView_SimulateAndRender + 196
    26  MyApp_iOS                           0x0026e1cc MyApp_iOS_EAGLView_MainLoopTimerCallback + 296
    27  MyApp_iOS                           0x009a7dfc MonoTouch_Foundation_NSActionDispatcher_Apply + 44
    28  MyApp_iOS                           0x00912540 wrapper_runtime_invoke_object_runtime_invoke_dynamic_intptr_intptr_intptr_intptr + 200
    29  MyApp_iOS                           0x00acc9c4 mono_jit_runtime_invoke + 2800
    30  MyApp_iOS                           0x00bd3ea4 mono_runtime_invoke + 140
    31  MyApp_iOS                           0x00c7d214 monotouch_trampoline + 2840
    32  Foundation                          0x3363b469 __NSFireTimer + 136
    33  CoreFoundation                      0x33a770a3 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 14
    34  CoreFoundation                      0x33a76b5b __CFRunLoopDoTimer + 850
    35  CoreFoundation                      0x33a481b5 __CFRunLoopRun + 1088
    36  CoreFoundation                      0x33a47c87 CFRunLoopRunSpecific + 230
    37  CoreFoundation                      0x33a47b8f CFRunLoopRunInMode + 58
    38  GraphicsServices                    0x33b0e4ab GSEventRunModal + 114
    39  GraphicsServices                    0x33b0e557 GSEventRun + 62
    40  UIKit                               0x32099329 -[UIApplication _run] + 412
    41  UIKit                               0x32096e93 UIApplicationMain + 670
    42  MyApp_iOS                           0x009d484c wrapper_managed_to_native_MonoTouch_UIKit_UIApplication_UIApplicationMain_int_string___intptr_intptr + 240
    43  MyApp_iOS                           0x009b4c00 MonoTouch_UIKit_UIApplication_Main_string__ + 36
    44  MyApp_iOS                           0x00269694 MyApp_iOS_Application_Main_string__ + 128
    45  MyApp_iOS                           0x00912540 wrapper_runtime_invoke_object_runtime_invoke_dynamic_intptr_intptr_intptr_intptr + 200
    46  MyApp_iOS                           0x00acc9c4 mono_jit_runtime_invoke + 2800
    47  MyApp_iOS                           0x00bd3ea4 mono_runtime_invoke + 140
    48  MyApp_iOS                           0x00bd6f3c mono_runtime_exec_main + 784
    49  MyApp_iOS                           0x00bd5f6c mono_runtime_run_main + 1048
    50  MyApp_iOS                           0x00ad7940 mono_jit_exec + 216
    51  MyApp_iOS                           0x00ac2e38 main + 3536
    52  MyApp_iOS                           0x000133a0 start + 52

Debug info from gdb:


=================================================================
Got a SIGABRT while executing native code. This usually indicates
a fatal error in the mono runtime or one of the native libraries
used by your application.
=================================================================

只是好奇,你的应用程序需要使用2.5 MB的托管内存做什么?另外,你是在使用MonoTouch 4.x还是之前的版本3.2.x(我想是)? - jonathanpeppers
1
我将关注这个问题... - tomfanning
Jonathan,这在3.2.x和4.x MonoTouch中都会发生。在这种情况下,我正在创建一个640x960 RGBA缓冲区以准备OpenGL纹理。值得一提的是,我经常创建更大的非托管缓冲区(例如1024x1024纹理的4MB),完全没有任何问题。我甚至可以运行一个压力测试,每帧创建那些4MB的非托管缓冲区,同时在单独的线程中不断调用GC.Collect(),它是完全稳定的。只有当我尝试分配大型托管缓冲区时,它才变得不稳定。 - Mike
我在https://dev59.com/Fuo6XIcBkEYKwwoYTSsu发布了一个问题,可能与这个问题有关(并且也链接到这个问题)。 - mj2008
Mike,我们根据你的报告进一步调查后找到了可能的原因,并正在解决中。很抱歉给你带来不便。 - Geoff Norton
Geoff,太棒了!感谢你的提醒。如果你想让我测试修复程序是否能解决我应用程序中的问题,请告诉我。 - Mike
1个回答

1

Darwin内核过度承诺内存。

这意味着,如果您请求200兆字节的RAM,即使它们不可用,只要您不实际使用内存,您的应用程序就会运行良好。

只有当您实际写入页面时,页面才会分配给您的进程。

一个适当的测试需要您分配内存,然后填满已使用的内存,这就是为什么您可能会认为即使您可能没有这样的内存,也可以分配200兆字节的RAM。

一个简单的程序将向您展示这一点:尝试分配500兆字节,操作系统会说“是的,您得到了它”,但是没有iPhone具有这种内存。

一个示例测试用例将大大显示问题所在。


Miguel,问题是,在我的测试中,每帧我都在接触那200MB。我的测试案例是运行我的应用程序,并在每个31字节中读取和写入那200MB。这会使帧速率变慢,但应用程序仍然可以正常运行。然后我尝试分配2.5MB的托管内存,它在GC_remap中崩溃。然后,我不分配那200MB,当我请求2.5MB的托管内存时,仍然会崩溃。 - Mike
而且,正如上面所述,这种崩溃并不是每次请求2.5MB时都会发生,而是随机发生。如果我有一个后台线程频繁调用GC.Collect(),或者我在调试器中运行,则崩溃发生的频率要高得多。我在这里发布了这个线程,谈论了使用WebRequests时的mprotect错误,并且两周前我在Novell的网站上提交了这个错误 - Mike
没有测试用例,很难找出你出了什么问题。 - miguel.de.icaza

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接