What does Addressables.Release actually do?

I cant for the life of me understand some errors that im getting whenever i release/dont release certain handles. It seems almost incosistent. I would like to know what this method does in the following scenario:

AsyncOperationHandle downloadHandle = Addressables.DownloadDependenciesAsync(key);
Addressables.Release(downloadHandle);

AsyncOperationHandle loadAssetHandle = Addressables.LoadAssetAsync<T>(key);
Addressables.Release(loadAssetHandle);

In some cases, releasing/not releasing the download handle completely breaks a subsequent load operation (which doesnt happen instantly, it happens like 3 seconds later) witha “Unable to load dependent bundle” error, when the bundle has absolutely 0 dependencies to any other group or viceversa.

  1. What does the Release on a download operation do? Does it compelety delete the assets it downloaded or does it just destroy the handle while the results still exist in memory availabe to be used? Is there any merit in doing so? When should I releae a download handle? What cons are there to not releasing it?

  2. What does the Release on a load operation do? When should i call this?

  3. What happens when I for example do something like this:

  • Download a label, instantly release the download handle and then Load the assets of the same label. Does the system need to re-download everything or is it cached from the download?

I have tried these tests but the results are absolutely incosistent even with clearing the cache.

An operation handle is a wrapper that holds an operation. When you call Acquire and Release we increment and decrement an internal reference counter. When the counter reaches 0 then we call Release on the dependent operations. So calling Release may not always have the same effect because the operation may have other references that have not been released yet.

The release does not delete any assets, it just unloads them(if the reference counter is 0). The assets are usually stored in a cache so subsequent calls to load should in theory be quicker.
Correctly using Acquire and Release is important for memory management. If you want the assets/bundles to be unloaded when you are no longer using them then it’s important to release the handles when you no longer need them. Handles don’t auto-release so you need to do it with them all unless you are using an API that states that it will auto-release.

If it’s an asset then Releasing it will do the decrement of the reference count.
Take the following simplified example:

Asset Bundle A:

  • My Asset 1
  • My Asset 2

Let’s say you loaded My Asset 1 and My Asset 2.
The Asset Bundle A would now have a reference count of 2.
If you release My Asset 1 then it would decrement to 1 but the bundle would still be loaded so you may get away with keeping a reference to My Asset 1.
If you then call Release on My Asset 2 the bundle reference count becomes 0 and its unloaded, My Asset 1 and 2 are destroyed.

Difficult to say. We don’t support canceling operations so they will likely continue and then release after they finish.
The behavior will be inconsistent because a lot of this will be based on timings and the cache.

Try to think of the operations as the handle to the actual asset you are using. If you plan to use this asset in multiple places then consider calling Acquire for every additional reference. When you no longer need it you call Release. Releasing an operation when you still need it is not recommended but will give varying results due to the reference count, if it still has references elsewhere then you get lucky and the asset is not unloaded but it may be unloaded at a later time when those references are released.

Some more info here https://docs.unity3d.com/Packages/com.unity.addressables@1.20/manual/MemoryManagement.html

The issue of calling Addressables.DownloadDependenciesAsync following by Addressables.LoadAssetAsync is a regression introduced in Addressables 1.21.15:

  • Before Addressables 1.21.12: you can use Addressables.DownloadDependenciesAsync following by Addressables.LoadAssetAsync. The downloaded asset bundles won’t be loaded into memory until Addressables.LoadAssetAsync is called. This is the expected behaviour.
  • In Addressables 1.21.12: you can still use Addressables.DownloadDependenciesAsync following by Addressables.LoadAssetAsync. But the downloaded asset bundles will be loaded into memory inside Addressables.DownloadDependenciesAsync.
  • In Addressables 1.21.15 and later versions: you can’t use Addressables.DownloadDependenciesAsync following by Addressables.LoadAssetAsync. Because the AssetBundleResource instance which is created inside Addressables.DownloadDependenciesAsync won’t load the asset bundle and Addressables.LoadAssetAsync will use this AssetBundleResource instance directly, which doesn’t have an asset bundle.

Calling Addressables.Release(downloadHandle) will release the AssetBundleResource instance created inside Addressables.DownloadDependenciesAsync. After that, when Addressables.LoadAssetAsync is called, it will create a new AssetBundleResource instance which can load an asset bundle.

Here is a part of the diff of AssetBundleProvider.cs for Addressables 1.21.12:

@@ -423,23 +422,6 @@ namespace UnityEngine.ResourceManagement.ResourceProviders
         /// <returns>The asset bundle.</returns>
         public AssetBundle GetAssetBundle()
         {
-            if (m_AssetBundle == null)
-            {
-                if (m_downloadHandler != null)
-                {
-                    m_AssetBundle = m_downloadHandler.assetBundle;
-                    m_downloadHandler.Dispose();
-                    m_downloadHandler = null;
-                }
-                else if (m_RequestOperation is AssetBundleCreateRequest)
-                {
-                    m_AssetBundle = (m_RequestOperation as AssetBundleCreateRequest).assetBundle;
-                }
-            }
-
-#if ENABLE_ADDRESSABLE_PROFILER
-            AddBundleToProfiler(Profiling.ContentStatus.Active, m_Source);
-#endif
             return m_AssetBundle;
         }

@@ -737,6 +734,9 @@ namespace UnityEngine.ResourceManagement.ResourceProviders
 #if ENABLE_ADDRESSABLE_PROFILER
                     AddBundleToProfiler(Profiling.ContentStatus.Active, m_Source);
 #endif
+                    m_AssetBundle = downloadHandler.assetBundle;
+                    downloadHandler.Dispose();
+                    downloadHandler = null;
                     m_ProvideHandle.Complete(this, true, null);
                     m_Completed = true;
                 }

As you can see, the code insde GetAssetBundle method was removed and downloadHandler.assetBundle was used to load the asset bundle, regradless of whether it’s downloading or not.

Here is a part of the diff of AssetBundleProvider.cs for Addressables 1.21.15:

@@ -734,7 +741,11 @@ namespace UnityEngine.ResourceManagement.ResourceProviders
#if ENABLE_ADDRESSABLE_PROFILER
                     AddBundleToProfiler(Profiling.ContentStatus.Active, m_Source);
#endif
-                    m_AssetBundle = downloadHandler.assetBundle;
+                    if (!(m_ProvideHandle.Location is DownloadOnlyLocation))
+                    {
+                        // this loads the bundle into memory which we don't want to do with download only bundles
+                        m_AssetBundle = downloadHandler.assetBundle;
+                    }
                     downloadHandler.Dispose();
                     downloadHandler = null;
                     m_ProvideHandle.Complete(this, true, null);

As you can see, the code was changed to not load the asset bundle when it’s downloading, but GetAssetBundle method still returns m_AssetBundle directly, which is null.

This issue still exists in latest versions(1.21.19 and 2.0.6 as for now).

2 Likes

Hi,
A lot of this is what you already know, but we wanted to put out a statement about this issue to help consolidate questions. Thanks for all you research into the issue and thorough documentation.

1 Like

I am sorry if this is outside of the topic but,
Is there any page with some clear and simply put information regarding the memory management in Unity? Especially dealing with Addressables.
I am totally confused because when profiling the memory while using addressables I just cannot understand whether the asset is unloaded or not? I mean, I see the reference count gets set to 0 when I am releasing all async operation handles. But the profiler cannot tell any major difference???

Let’s assume I have a heavy-sized video (227mb) in my addressables’ group that is being used as a local group (built-in in the build).

  1. I create an AssetReference to that video.
  2. Then I successfully load the video using AssetReference.LoadAssetAsync().
  3. I play the video for a few seconds and then I stop it.
  4. I destroy the video player and do AssetReference.ReleaseAsset().

When taking memory snapshots during all of these steps the memory change difference is insignificant (~10mb).
Can somebody explain this to me? Perhaps I am loading the assets incorrectly?
How can I know if the asset is 100% released from the memory? Should I break my asset bundle into labels? Should I do something else?

1 Like