The garbage collector used by Unity (LibGC - the Boehm collector) is quite greedy (its OS memory footprint only increases over time), and it allocates more RAM than strictly needed to reduce the frequency of collections. By default LibGC uses a setting that grows the heap by approx. 33% more RAM than needed at any time:
GC_API GC_word
GC_free_space_divisor;
/* We try to make sure that we allocate at */
/* least N/GC_free_space_divisor bytes between */
/* collections, where N is the heap size plus */
/* a rough estimate of the root set size. */
/* Initially, GC_free_space_divisor = 3. */
/* Increasing its value will use less space */
/* but more collection time. Decreasing it */
/* will appreciably decrease collection time */
/* at the expense of space. */
/* GC_free_space_divisor = 1 will effectively */
/* disable collections. */
The LibGC internal function GC_collect_or_expand() bumps up the # of blocks to get from the OS by a factor controlled by this global variable. So if we increase this global (GC_free_space_divisor) LibGC should use less memory. Luckily, you don't need Unity source code to change this LibGC variable because it's global. (Note: At least on iOS. On other platforms with dynamic libraries changing this sucker may be trickier - but doable.)
In our game (Dungeon Boss), without changing this global, our Mono reserved heap size is 74.5MB in the first level. Setting this global to 16 at the dawn of time (from our main script's Start() method) reduces this to 61.1MB, for a savings of ~13.3MB of precious RAM. The collection frequency is increased, because the Mono heap will have less headroom to grow between collections, but it's still quite playable.
Bumping up this divisor to 64 saves ~23MB of RAM, but collections occur several times per second (obviously unplayable).
To change this global in Unity iOS projects, you'll need to add a .C/CPP (or .M/.MM) file into your project with this helper:
typedef unsigned long GC_word;
extern GC_word GC_free_space_divisor;
void bfutils_SetLibGCFreeSpaceDivisor(int divisor)
{
GC_free_space_divisor = divisor;
}
While you're doing this, you can also add these two externs so you can directly monitor LibGC's memory usage (directly bypassing Unity's Profiler class, which I think only works in Development builds):
extern "C" size_t GC_get_heap_size();
extern "C" size_t GC_get_free_bytes();
Now in your .CS code somewhere you can call this method:
[DllImport("__Internal")] private static extern void bfutils_SetLibGCFreeSpaceDivisor(int divisor);
public static void ConfigureLibGC()
{
// The default divisor is 3. Anything higher saves memory, but causes more frequent collections.
bfutils_SetLibGCFreeSpaceDivisor(16);
}
Just remember, if you change this setting LibGC will collect more frequently. But if your code uses aggressive object pooling (like it should) this shouldn't be a big deal.
Also note that this global only impacts the amount of Mono heap memory headroom that LibGC tries to keep around. This global won't help you if you spike up your Mono heap's size by temporarily allocating very large blocks on the Mono heap. Large temp allocations should be avoided on the Mono heap because once LibGC gets its tentacles on some OS memory it doesn't ever let it go.