Why Your GetComponent Allocates Memory in the Editor but Not in Builds

Here’s a trap that has sent countless Unity developers chasing a performance ghost: you open the Profiler, see GC.Alloc next to your GetComponent calls, and spend an afternoon “optimizing” allocations that don’t exist in your actual build.

This post untangles what’s real and what’s a measurement artifact — and how to profile garbage collection so the numbers actually mean something.

The artifact: GetComponent allocates in the Editor, not in builds

The Unity Editor works differently from a build, and this directly affects your profiling data. Specifically: GetComponent always allocates memory when run in the Editor, but not in a built project.

So if the in-Editor Profiler shows GC allocations attributed to GetComponent, those allocations may simply not happen on device. Optimizing them away changes nothing in the build — you’re tuning the measurement tool, not the game.

This is the reason the golden rule of Unity profiling exists:

Profile a development build on your target device — not the Editor — whenever you care about real allocation and timing numbers.

What is real: GetComponent’s CPU cost

Don’t over-correct and decide GetComponent is free. It isn’t. Even in a build, GetComponent (and the built-in component accessors like camera, rigidbody, etc.) carry a real CPU cost per call. The fix is the one you already know, and it’s still correct:

Cache the reference once, reuse it.

// ❌ Re-resolves the component every frame
void Update() {
    GetComponent<Rigidbody>().AddForce(force);
}

// ✅ Resolve once, cache it
Rigidbody _rb;
void Awake() {
    _rb = GetComponent<Rigidbody>();
}
void Update() {
    _rb.AddForce(force);
}

Cache in Awake (or Start) rather than calling repeatedly in Update. The win here is CPU time, not (build-time) GC — but it’s a real win and it’s free.

The real GC problem: allocations inside Update

If you are seeing genuine GC spikes on device, they usually come from allocating reference types inside hot methods — new lists, arrays, class instances, or strings created every frame. Frequent allocations grow the managed heap, and when the collector runs it can stop the main thread long enough to produce a visible stutter — tens to hundreds of milliseconds. These are the spikes you see in the Profiler’s frame-time graph.

The biggest offenders inside Update:

  • New collections every frame. Pre-allocate a List/array once and Clear() it before reuse instead of new-ing it each frame.
  • String concatenation. Strings are immutable in C#, so every concat/format allocates a new string. Keep it out of per-frame code (or use StringBuilder / cached strings).
  • Temporary class instances. Prefer structs for short-lived data, or cache and reuse the object.
  • LINQ and boxing. Convenient, but frequently allocates. Avoid in hot paths.

Profile it correctly

To find the real source of allocations:

  1. Build a development build and run it on the target device, then connect the Profiler. (Editor numbers include artifacts like the GetComponent allocation above.)
  2. In the Profiler, enable Call Stacks mode for GC.Alloc samples — it gives you the exact call site of each allocation without the overhead of Deep Profiling.
  3. Look at the frame-time graph for spikes that line up with GC activity.

Worth knowing: since Unity 2019, Incremental Garbage Collection spreads collection work across multiple frames instead of one long stop-the-world pause, which reduces the spike — it doesn’t make collection cheaper overall, and it adds a little overhead, but it smooths the stutter. It’s a mitigation, not a substitute for cutting the allocations.

Let PerfLint flag the per-frame offenders

Knowing the rules is one thing; finding every GetComponent, Camera.main, FindObjectOfType, string concat, and LINQ call sitting inside an Update/LateUpdate/ FixedUpdate across a whole project is tedious and easy to miss. PerfLint for Unity scans your scripts locally and flags exactly these per-frame GC and CPU patterns, located to the line, with severity and a recommended fix (PERF.UPD001). Nothing is uploaded — analysis runs entirely on your machine. Run a free scan →


FAQ

Does GetComponent allocate garbage? In the Editor, yes — always. In a build, no. If the Profiler shows GC.Alloc from GetComponent in the Editor, confirm it on a development build on-device before optimizing.

Should I still cache GetComponent then? Yes. Even without the allocation, GetComponent has a per-call CPU cost. Cache the reference in Awake/Start and reuse it instead of calling it in Update.

Why do I get frame stutters but a fine average FPS? Classic GC spike signature. Garbage collection can pause the main thread for tens to hundreds of ms occasionally — barely moving the average but very visible as stutter. Cut per-frame allocations and consider Incremental GC.

How do I find what’s allocating? Profile a development build on-device with Profiler Call Stacks mode enabled for GC.Alloc samples — it points to the exact line without Deep Profiling overhead.


← Back to all posts