munmap() doesn't return pages to system on macOS Sierra

I have an application which operates with memory a lot. This app is written in c# - it has mono runtime under the hood.


We identfied that on Sierra the app memory constantly grows when it does consuming memory operations. Same time the issue is not reproducible on El Captain.


It can even achive 8 or 10 GB if it works a long period of time (weeks).


I have verified that there is no memory leaks (significant, which can cause such a growth). I have verified that there is no retain cycles preventing objects from being collected - again the app works perfect on El Captain.


I found that:

- Almost all app memory (excluding 150 mb of active) moves to compressed state and even swaped to disk then.

- According to Xcode Instruments VM Tracker tool - VM_Allocation region holds almost all swapped memory


<---!!! - pay attention to


% of Res. Type # Regs Path Resident Size Dirty Size Swapped Size Virtual Size Res. %
100% *All* 17997 < multiple > 517.21 MiB 240.89 MiB 704.31 MiB <---!!! 2.07 GiB 24%


% of Res. Type # Regs Path Resident Size Dirty Size Swapped Size Virtual Size Res. %
18% VM_ALLOCATE 15939 - 90.76 MiB 90.76 MiB 588.81 MiB <---!!! 696.29 MiB 13%


I dtrace-ed all occurences and stack traces of virtual memory allocation/ deallocations - mmap(), munmap(), vm_allocate(), vm_deallocate() and found that:


For each mmap() of currently swapped memory pages there was corresponding successfull munmap()

For each vm_allocate() of currently swapped memory pages there was corresponding successfull vm_deallocate()


but


for some reason munmap() of some vm region returns "0" which means success but associated pages are still presented - they are not returned to system.


That's not expected - munmap() of private, ananymous memory should lead to page freeing. I verified that in my sample app. Any manipulations with mlock(), madvice() did not help to imitate this.


That causes an issues as that pages are abandoned by GC (they are not reused anymore) same time they are not free. As they are no longer in use they go thru compressing and then swapping to disk.


Moreover, abandoned pages are alive - access to them from lldb does not lead to BAD_ACCESS.


Has anyone met this?


Here are extractions from the logs which show the problem.


This is VM state evolution in 0x11c5XXXXX range of 1 MB:


I. allocation, deallocation object in Large objects space


Type Address Range Dirty Size Swapped Size Resident Size Virtual Size Resident % Protections Path
VM_ALLOCATE 0x11c500000 - 0x11c600000 1.00 MiB 0 Bytes 1.00 MiB 1.00 MiB 100% rw-/rwx


3    551                      mmap:return [240760570406181]:mmap: [0x11c500000, 2097152] flags: 4098
             libsystem_kernel.dylib`__mmap+0xa
             Drive`mono_valloc+0x69
             Drive`mono_valloc_aligned+0x22
             Drive`sgen_alloc_os_memory_aligned+0x23
             Drive`sgen_los_alloc_large_inner+0x274
             Drive`sgen_alloc_obj_nolock+0x69
             Drive`mono_gc_alloc_vector+0x7b
             0x1063cb0e6
             0x117429a4a
             0x1174296d2
             0x11742881c
             0x1174286ca
             0x117428530
             0x1173a77cd
             0x11739a81a
             0x11739a2e1
             0x11739a213
             0x116ad373b
             0x111a2f199
             0x10df54fc9


3    303                    munmap:return [240983284059591]:munmap: 0 0x11c500000, 1048576
             libsystem_kernel.dylib`__munmap+0xa
             Drive`mono_valloc_aligned+0xd0
             Drive`sgen_alloc_os_memory_aligned+0x23
             Drive`sgen_los_alloc_large_inner+0x274
             Drive`sgen_alloc_obj_nolock+0x69
             Drive`mono_gc_alloc_vector+0x7b
             0x1063cb0e6
             0x117429a4a
             0x1174296d2
             0x11742881c
             0x1174286ca
             0x117428530
             0x1173a77cd
             0x11739a81a
             0x11739a2e1
             0x11739a213
             0x116ad373b
             0x111a2f199
             0x10df54fc9
             Drive`mono_jit_runtime_invoke+0x8e0


II. SQLITE page cache allocation / deallocation


Type Address Range Dirty Size Swapped Size Resident Size Virtual Size Resident % Protections Path
SQLite page cache 0x11c50f000 - 0x11c51f000 64.00 KiB 0 Bytes 64.00 KiB 64.00 KiB 100% rw-/rwx
SQLite page cache 0x11c51f000 - 0x11c52f000 64.00 KiB 0 Bytes 64.00 KiB 64.00 KiB 100% rw-/rwx


[241059759675635]:vm_allocate: 0x11c50f000, 0x10000


             libsystem_kernel.dylib`vm_allocate+0x26
             libsqlite3.dylib`purgeableCacheFetch+0x32a
             libsqlite3.dylib`sqlite3PagerGet+0x18d
             libsqlite3.dylib`getAndInitPage+0x47
             libsqlite3.dylib`sqlite3BtreeMovetoUnpacked+0x52d
             libsqlite3.dylib`handleDeferredMoveto+0x1e
             libsqlite3.dylib`sqlite3VdbeExec+0x9b72
             libsqlite3.dylib`sqlite3_step+0x3cf
             0x11188e240


[241097285460026]:vm_deallocate: 0x11c50f000


             libsystem_kernel.dylib`vm_deallocate
             libsqlite3.dylib`purgeableCacheDestroy+0x464
             libsqlite3.dylib`sqlite3PagerClose+0x279
             libsqlite3.dylib`sqlite3BtreeClose+0x142
             libsqlite3.dylib`sqlite3LeaveMutexAndCloseZombie+0x192
             libsqlite3.dylib`sqlite3Close+0x409
             0x112caf100
             0x112caee8d
             0x11188ec28
             0x112fee6d5
             Drive`mono_gc_run_finalize+0x3fc
             Drive`sgen_gc_invoke_finalizers+0x114
             Drive`finalizer_thread+0x252
             Drive`start_wrapper+0x22e
             Drive`inner_start_thread+0x196
             libsystem_pthread.dylib`_pthread_body+0xb4
             libsystem_pthread.dylib`_pthread_body
             libsystem_pthread.dylib`thread_start+0xd


III. SQLITE page cache deallocation. These three guys are unknown... That's strange - my assumption that the reason that I don't have allocation stack trace for them is dtrace failure - it reports errors a little from time to time


Type Address Range Dirty Size Swapped Size Resident Size Virtual Size Resident % Protections Path
VM_ALLOCATE 0x11c5ef000 - 0x11c5f1000 8.00 KiB 0 Bytes 8.00 KiB 8.00 KiB 100% rw-/rwx
VM_ALLOCATE 0x11c5f1000 - 0x11c5f2000 4.00 KiB 0 Bytes 4.00 KiB 4.00 KiB 100% rw-/rwx
VM_ALLOCATE 0x11c5f5000 - 0x11c5fa000 20.00 KiB 0 Bytes 20.00 KiB 20.00 KiB 100% rw-/rwx


IV. FIRST REPRODUCTION <---!!! 0x11c51e000 - mmapped() then munmapped() but still be presented.
Type Address Range Dirty Size Swapped Size Resident Size Virtual Size Resident % Protections Path
SQLite page cache 0x11c50e000 - 0x11c51e000 0 Bytes 0 Bytes 0 Bytes 64.00 KiB 0% rw-/rwx
VM_ALLOCATE 0x11c51e000 - 0x11c529000 44.00 KiB 0 Bytes 44.00 KiB 44.00 KiB 100% rw-/rwx
VM_ALLOCATE 0x11c5ef000 - 0x11c5f1000 8.00 KiB 0 Bytes 8.00 KiB 8.00 KiB 100% rw-/rwx
VM_ALLOCATE 0x11c5f1000 - 0x11c5f2000 4.00 KiB 0 Bytes 4.00 KiB 4.00 KiB 100% rw-/rwx
VM_ALLOCATE 0x11c5f5000 - 0x11c5fa000 20.00 KiB 0 Bytes 20.00 KiB 20.00 KiB 100% rw-/rwx


0    551                      mmap:return [241237210309703]:mmap:   [0x11c51e000, 4096] flags: 4098
             libsystem_kernel.dylib`__mmap+0xa
             Drive`mono_valloc+0x69
             Drive`mono_lock_free_alloc+0x335
             Drive`sgen_alloc_internal+0x3b
             Drive`sgen_gray_object_enqueue+0x5c
             Drive`mono_g_hash_mark+0x11a
             Drive`job_scan_from_registered_roots+0x18c
             Drive`sgen_workers_enqueue_job+0x23
             Drive`enqueue_scan_from_roots_jobs+0x7d
             Drive`major_copy_or_mark_from_roots+0x36a
             Drive`major_start_collection+0x155
             Drive`major_do_collection+0x4e
             Drive`sgen_perform_collection+0x2a6
             Drive`sgen_los_alloc_large_inner+0x5d
             Drive`sgen_alloc_obj_nolock+0x69
             Drive`mono_gc_alloc_vector+0x7b
             0x1063cb0e6
             0x10bd2a7b3
             0x10bd293d1
             0x1173a6db6

2    303                    munmap:return [241257343803100]:munmap: 0    <---!!!    0x11c51e000, 4096
             libsystem_kernel.dylib`__munmap+0xa
             Drive`mono_vfree+0x32
             Drive`desc_retire+0x43
             Drive`mono_lock_free_free+0x1a9
             Drive`pin_stats_tree_free+0x20
             Drive`pin_stats_tree_free+0x20
             Drive`pin_stats_tree_free+0x17
             Drive`pin_stats_tree_free+0x17
             Drive`pin_stats_tree_free+0x20
             Drive`pin_stats_tree_free+0x17
             Drive`pin_stats_tree_free+0x17
             Drive`pin_stats_tree_free+0x20
             Drive`pin_stats_tree_free+0x20
             Drive`pin_stats_tree_free+0x20
             Drive`pin_stats_tree_free+0x20
             Drive`pin_stats_tree_free+0x20
             Drive`pin_stats_tree_free+0x20
             Drive`sgen_pin_stats_reset+0x10
             Drive`collect_nursery+0x414
             Drive`sgen_perform_collection+0x1b1


V. Done - all the memory in the range is abandoned


Type Address Range Dirty Size Swapped Size Resident Size Virtual Size Resident % Protections Path
VM_ALLOCATE 0x11c51c000 - 0x11c51e000 0 Bytes 8.00 KiB 0 Bytes 8.00 KiB 0% rw-/rwx
VM_ALLOCATE 0x11c51e000 - 0x11c53c000 0 Bytes 120.00 KiB 0 Bytes 120.00 KiB 0% rw-/rwx
VM_ALLOCATE 0x11c53c000 - 0x11c550000 0 Bytes 80.00 KiB 0 Bytes 80.00 KiB 0% rw-/rwx
VM_ALLOCATE 0x11c550000 - 0x11c568000 0 Bytes 96.00 KiB 0 Bytes 96.00 KiB 0% rw-/rwx
VM_ALLOCATE 0x11c568000 - 0x11c569000 0 Bytes 4.00 KiB 0 Bytes 4.00 KiB 0% rw-/rwx
VM_ALLOCATE 0x11c569000 - 0x11c57d000 0 Bytes 80.00 KiB 0 Bytes 80.00 KiB 0% rw-/rwx
VM_ALLOCATE 0x11c57d000 - 0x11c589000 0 Bytes 48.00 KiB 0 Bytes 48.00 KiB 0% rw-/rwx
VM_ALLOCATE 0x11c589000 - 0x11c58b000 0 Bytes 8.00 KiB 0 Bytes 8.00 KiB 0% rw-/rwx
VM_ALLOCATE 0x11c58b000 - 0x11c58c000 0 Bytes 4.00 KiB 0 Bytes 4.00 KiB 0% rw-/rwx
VM_ALLOCATE 0x11c58c000 - 0x11c594000 0 Bytes 32.00 KiB 0 Bytes 32.00 KiB 0% rw-/rwx
VM_ALLOCATE 0x11c594000 - 0x11c5b3000 0 Bytes 124.00 KiB 0 Bytes 124.00 KiB 0% rw-/rwx
VM_ALLOCATE 0x11c5b3000 - 0x11c5b5000 0 Bytes 8.00 KiB 0 Bytes 8.00 KiB 0% rw-/rwx
VM_ALLOCATE 0x11c5b5000 - 0x11c5c9000 0 Bytes 80.00 KiB 0 Bytes 80.00 KiB 0% rw-/rwx
VM_ALLOCATE 0x11c5c9000 - 0x11c5cd000 0 Bytes 16.00 KiB 0 Bytes 16.00 KiB 0% rw-/rwx
VM_ALLOCATE 0x11c5cd000 - 0x11c5d1000 0 Bytes 16.00 KiB 0 Bytes 16.00 KiB 0% rw-/rwx
VM_ALLOCATE 0x11c5d1000 - 0x11c5df000 0 Bytes 56.00 KiB 0 Bytes 56.00 KiB 0% rw-/rwx
VM_ALLOCATE 0x11c5df000 - 0x11c5ee000 0 Bytes 60.00 KiB 0 Bytes 60.00 KiB 0% rw-/rwx
VM_ALLOCATE 0x11c5ee000 - 0x11c5ef000 0 Bytes 4.00 KiB 0 Bytes 4.00 KiB 0% rw-/rwx
VM_ALLOCATE 0x11c5ef000 - 0x11c5f1000 0 Bytes 8.00 KiB 0 Bytes 8.00 KiB 0% rw-/rwx
VM_ALLOCATE 0x11c5f1000 - 0x11c5f5000 0 Bytes 16.00 KiB 0 Bytes 16.00 KiB 0% rw-/rwx
VM_ALLOCATE 0x11c5f5000 - 0x11c5fa000 0 Bytes 20.00 KiB 0 Bytes 20.00 KiB 0% rw-/rwx
VM_ALLOCATE 0x11c5fa000 - 0x11c619000 0 Bytes 124.00 KiB 0 Bytes 124.00 KiB 0% rw-/rwx

Replies

You should put the results of your investigation into a bug report.

Please post your bug number, just for the record.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks Quinn for advice!


I have logged a bug as you recommended (#29750631).