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.