Back to blog
·FrameDoctor Team

How to Reduce GC Allocations in Unity: A Practical Guide

GC allocations are the silent killer of smooth gameplay. Learn the most common causes and how to eliminate them from your Unity project.

unitycpugc-allocationsoptimization

Garbage collection spikes are one of the most common causes of frame hitches in Unity games. Every time the managed heap fills up, the GC pauses your game to clean up — and on mobile, that can mean 5-15ms of lost frame time.

In this guide, we'll cover the most impactful sources of GC allocations and how to fix them.

Why GC Allocations Matter

Unity uses a non-generational, non-compacting garbage collector (Boehm GC). This means:

  • Collections scan the entire managed heap
  • The larger the heap, the longer the pause
  • Fragmentation accumulates over time
  • There's no way to predict *when* a collection will trigger
  • For a 60 FPS game, you have 16.67ms per frame. A single GC spike can eat 8-12ms of that budget.

    The Biggest Offenders

    1. String Concatenation in Update Loops

    // BAD: Allocates a new string every frame
    void Update() {
        scoreText.text = "Score: " + score.ToString();
    }
    
    // GOOD: Use StringBuilder or cache
    private StringBuilder _sb = new StringBuilder(32);
    void Update() {
        _sb.Clear();
        _sb.Append("Score: ").Append(score);
        scoreText.text = _sb.ToString();
    }

    2. LINQ in Gameplay Code

    // BAD: LINQ allocates enumerators and delegates
    var enemies = allEntities.Where(e => e.IsEnemy).ToList();
    
    // GOOD: Pre-allocated list with manual filtering
    private List<Entity> _enemyBuffer = new List<Entity>(64);
    void GetEnemies() {
        _enemyBuffer.Clear();
        for (int i = 0; i < allEntities.Count; i++) {
            if (allEntities[i].IsEnemy)
                _enemyBuffer.Add(allEntities[i]);
        }
    }

    3. Boxing Value Types

    // BAD: Boxing int to object
    void LogDamage(object value) { }
    LogDamage(42); // Boxes the int
    
    // GOOD: Use generics
    void LogDamage<T>(T value) { }
    LogDamage(42); // No boxing

    4. Instantiate / Destroy

    // BAD: Creates garbage from destroyed objects
    void Fire() {
        var bullet = Instantiate(bulletPrefab);
        Destroy(bullet, 3f);
    }
    
    // GOOD: Object pooling
    void Fire() {
        var bullet = _bulletPool.Get();
        bullet.Activate(3f); // Returns to pool after 3s
    }

    How to Find GC Allocations

  • Open the Unity Profiler (Window → Analysis → Profiler)
  • Enable the CPU Usage module
  • Play your game and look for GC.Alloc markers
  • Click on frames with high GC to see the call stack
  • Or use FrameDoctor — upload your profiler capture and get every allocation source identified automatically with specific code fixes.

    Quick Rules

  • Never allocate in Update/FixedUpdate/LateUpdate
  • Pre-allocate collections with expected capacity
  • Use object pools for anything instantiated/destroyed frequently
  • Cache component references in Awake/Start, not every frame
  • Avoid closures and delegates in hot paths
  • Further Reading

  • Unity Manual: Understanding Automatic Memory Management
  • Unity Best Practices: Memory Management

  • *Want to find every GC allocation in your project automatically? Try FrameDoctor free — upload your profiler data and get a prioritized list of allocations with code fixes in seconds.*