,

How Do You Prevent Garbage Collection Spikes in Unity?

C# code displayed on a laptop while profiling Unity garbage collection

To prevent garbage collection spikes in Unity, profile a development build on the target device, find code that allocates managed memory repeatedly, and remove those allocations from gameplay loops. Reuse collections and buffers, update UI only when values change, pool short-lived objects, and leave incremental garbage collection enabled unless measurements show a reason to change it. The goal is not “never allocate.” It is to avoid a constant stream of temporary objects during play.

This matters on every platform, but mobile devices and standalone VR headsets make inconsistent frame time especially visible. A reasonable average frame rate can still feel rough when one frame pauses for managed-memory cleanup. Allocation patterns deserve the same attention as draw calls, physics, and shaders.

What actually causes a GC spike?

C# automatically manages objects created on the managed heap. When Unity needs space and cannot satisfy a new allocation, its garbage collector examines heap objects, identifies those no longer referenced, and reclaims their memory. That convenience prevents many manual-memory errors, but the collection work consumes CPU time.

Unity’s current documentation recommends reducing frequent managed allocations to 0 bytes per frame, or as close to zero as practical. A tiny allocation repeated every frame accumulates quickly; the problem is usually repetition rather than one isolated object. A menu or level-loading allocation may be acceptable, while the same allocation in Update, a physics callback, or an XR interaction loop can continually feed the collector.

Start with evidence from the Unity Profiler

Capture a representative player build

Build a Development Player, connect the Unity Profiler, and reproduce a representative section on the actual Android, iOS, or VR device. Editor-only systems can add noise that is absent from a player build.

  1. Open Window > Analysis > Profiler.
  2. Select the CPU Usage module and record normal gameplay.
  3. Use Hierarchy view and inspect the GC.Alloc column.
  4. Sort by allocations and expand the repeating samples to their script methods.
  5. Change one hotspot, rebuild, and compare the same test sequence.

Trace the repeating caller

Unity documents that the GC.Alloc column reports managed bytes allocated for the selected frame and thread. The Memory Profiler module shows broader memory use, while the separate Memory Profiler package helps compare detailed snapshots. For a recurring stutter, however, the CPU module is often the fastest starting point.

Audit the common allocation sources

Pattern in hot code Why it creates pressure Better direction
new List<T>() or a new array each scan Creates a fresh managed object repeatedly Keep one buffer and clear or overwrite it
String interpolation every frame Produces temporary strings Refresh text only when the value changes
LINQ chains in frequent loops Can create enumerators and temporary collections Use a direct loop in measured hotspots
Lambdas that capture local variables Create closure objects Cache delegates or use a non-capturing method
Passing value types as object Can box the value on the heap Use strongly typed overloads and collections
Repeated Instantiate/Destroy Creates and discards objects and components Pool frequently reused effects or targets

Prioritize frequency over size

These patterns are not universally wrong. Concentrate on code that runs often and appears in the profiler.

Move display work from every frame to state changes

A score, move count, health value, or connection label usually changes far less often than Update runs. Rebuilding its text every frame wastes CPU work and can create temporary strings. Instead, make gameplay state publish a change and update the display once.

This event-driven habit also improves architecture. The same separation described in Blekol’s ScriptableObject vs MonoBehaviour guide keeps data ownership clear: the model changes the value, and the view reacts. It becomes easier to profile because the expensive work has an obvious trigger.

Reuse buffers in repeated queries

Queries that return a new array are easy to write but dangerous inside a frequent scan. Where Unity provides a non-allocating API, supply a preallocated destination and reuse it. This simplified proximity scanner performs its physics query without creating a new result array on every call:

using UnityEngine;

public sealed class NearbyTargetScanner : MonoBehaviour
{
    [SerializeField] private float radius = 3f;
    [SerializeField] private LayerMask targetMask;

    private readonly Collider[] hits = new Collider[32];

    public int Scan()
    {
        int count = Physics.OverlapSphereNonAlloc(
            transform.position,
            radius,
            hits,
            targetMask,
            QueryTriggerInteraction.Ignore);

        for (int i = 0; i < count; i++)
        {
            // Read hits[i]; do not create a temporary results list here.
        }

        return count;
    }
}

Choose the buffer size from real gameplay limits and watch for count == hits.Length, which can mean the buffer filled. The same principle applies to reusable List<T> instances: create capacity once, call Clear, and refill the list.

Pool objects with a clear lifetime

Particles, projectiles, damage indicators, collectables, and temporary interaction markers are common pooling candidates. Unity 6.6 includes UnityEngine.Pool APIs for reusing frequently needed objects instead of constantly creating and destroying them. A useful pool has predictable ownership: get an object, reset all mutable state, release it once, and protect against double release.

Large pools consume memory, and complex reset logic can create bugs. Pool objects that repeat in measured hotspots; one-off scene props can remain ordinary objects.

What incremental GC can—and cannot—fix

Incremental garbage collection is enabled by default in Unity 6.6. It divides collection work across multiple frames, which can reduce a single long interruption. It does not reduce the total amount of allocation work your code creates. If gameplay continually produces garbage, incremental GC only distributes the cleanup.

Avoid treating System.GC.Collect() as a routine gameplay fix. A full collection is blocking, and manually disabling collection allows the heap to grow until memory becomes unsafe. Unity exposes manual controls for tightly bounded, expert workflows, but ordinary mobile and VR projects should first remove unwanted allocations and verify the result with profiling.

Apply the same rule differently on mobile and VR

In a mobile puzzle game such as Cubus for Android and iOS, inspect swipe handling, move counters, hints, effects, and level transitions. Input can be allocation-free while menus are allowed to allocate during controlled transitions. Readers can visit the Cubus page for the available download links and see how a focused control loop supports short play sessions.

In Periodic Table VR, close interaction, labels, spawned learning objects, and repeated physics queries deserve special attention. Test while moving, grabbing, and opening panels—not only while standing in an empty scene. Profile the worst realistic interaction sequence on the headset.

A practical zero-allocation gameplay checklist

  • Measure a Development Player on every target platform.
  • Check GC.Alloc during representative gameplay, not only menus.
  • Remove new arrays, lists, strings, closures, and boxing from frequent paths.
  • Update UI when data changes rather than every frame.
  • Reuse buffers and set realistic capacities.
  • Pool only short-lived objects that repeat often.
  • Keep incremental GC enabled initially, then validate with the profiler.
  • Retest after each change and watch both frame time and total memory.

The durable solution to garbage collection spikes is a workflow: capture the stutter, trace its allocations, change the smallest responsible system, and confirm the result on hardware. That discipline scales from a compact mobile puzzle to an interaction-heavy VR scene. Explore more development notes and games at Blekol Games.

Official references: Unity 6.6 documentation for the garbage collector, tracking GC allocations, reference-type management, object pooling, OverlapSphereNonAlloc, and garbage collection modes.

Featured photo by Bernd Dittrich on Unsplash.

2 responses to “How Do You Prevent Garbage Collection Spikes in Unity?”

Leave a Reply

You might also like