Quick answer: The handle owns the asset’s lifetime. Releasing it can unload the asset — references go null. Hold the handle as long as you use the asset; release in OnDestroy.

Code loads a prefab via Addressables, instantiates it, then releases the handle immediately. Later the prefab’s textures or the prefab itself read as null — it got unloaded.

Handle == Lifetime

An AsyncOperationHandle is a reference count. Release decrements it; at zero, Addressables unloads the asset and its dependencies. Releasing too early pulls the asset out from under you.

Hold the Handle

private AsyncOperationHandle<GameObject> _handle;

async void LoadEnemy()
{
    _handle = Addressables.LoadAssetAsync<GameObject>("Enemy");
    await _handle.Task;
    Instantiate(_handle.Result);
}

void OnDestroy()
{
    if (_handle.IsValid())
        Addressables.Release(_handle);
}

Keep the handle as a field; release it only when nothing needs the asset anymore.

InstantiateAsync Auto-Tracks

Addressables.InstantiateAsync ties the asset’s lifetime to the spawned GameObject. Releasing happens automatically when you Addressables.ReleaseInstance(go) or destroy it — less manual bookkeeping than LoadAssetAsync + Instantiate.

Reference Counting Shared Assets

Loading the same address twice gives two handles, both holding one ref. Release both. A leaked handle keeps the asset resident; an over-release nulls it for the other holder.

Verifying

Load, use, and keep the asset for a while — it stays valid. Release on destroy — memory drops. The Addressables event viewer shows balanced load/release.

“The handle is the lifetime. Hold it while you use the asset; release exactly once when done.”

Prefer InstantiateAsync / ReleaseInstance for spawned objects — it ties asset lifetime to the GameObject so you can’t release early.