Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Unexpected behavior of memory usage with links to ScriptableObject

Discussion in 'Scripting' started by crol, Mar 5, 2021.

  1. crol

    crol

    Joined:
    Aug 6, 2013
    Posts:
    30
    Found a strange problem with my project when too many assets were in memory even if I don't need them right now and after some investigation, I have found the problem and checked it on a small test project.

    So in my test project, there are:

    - prefab (PrefabWithMeshes) of Game Object with just a few meshes inside
    - scriptable object (ScriptableOne) and its instance with only one field - link to the prefab
    Code (CSharp):
    1.  
    2. [CreateAssetMenu(menuName = "Custom/ScriptableOne")]
    3. public class ScriptableOne : ScriptableObject
    4. {
    5.    public GameObject linkToBigPrefab;
    6. }
    7.  
    - on the scene there is an object with the simple script (LinksHolderOne):

    Code (CSharp):
    1. public class LinksHolderOne : MonoBehaviour
    2. {
    3.   public string mission01;
    4.   public ScriptableOne preset;
    5.  
    6.   public void LoadPreset()
    7.   {
    8.     preset = Resources.Load(mission01) as ScriptableOne;
    9.   }  
    10.  
    11.   public void DestrObject()
    12.   {
    13.     Destroy(gameObject);
    14.   }
    15. }
    All memory checks are made via profiler>take sample from the build.

    Situation one:

    Any direct link to ScriptableOne from code also loads to memory PrefabWithMeshes and even meshes from it.

    And same for calling LoadPreset() where it loads from Resources. Though it won't load to memory all prefab contents till I use Instantiate. This was unexpected for me but ok, I can leave with that.

    Situation two:

    Calling DestrObject() and using Resources.UnloadUnusedAssets(); GC.Collect(); is not unloading PrefabWithMeshes and its contents from memory. Changing the scene won't help either, PrefabWithMeshes is stuck in memory from this point. And there is no way to clean memory from it.

    But if I add this to LinksHolderOne:

    Code (CSharp):
    1. private void OnDestroy()
    2. {
    3.   preset = null;
    4. }
    and also call Resources.UnloadUnusedAssets(); GC.Collect(); the memory will be released!

    Is it working as intended and I have to clean all links to ScriptableObjects by myself before destroying objects which are holding them or am I missing something?
     
  2. csofranz

    csofranz

    Joined:
    Apr 29, 2017
    Posts:
    1,556
    First, you are not stating what 'working as intended' really means, you refer to a 'problem' without telling us what that problem really is; you perhaps believe that certain resources should load only when you want them to and they load unexpectedly for you. There seem to be a lot of implied assumptions that you never really spell out wrt when objects should versus when they are allocated and released, so there is very little I can do to help you. Moreover, you are also using terms that I'm not familiar with (what is a 'direct link'?) so I'm at a bit of a disadvantage here as well.

    All that being said, a couple of observations
    • Unity uses managed memory, meaning that normally, devs don't need to worry about memory allocation. Why are you looking into this?
    • IIRC there are 'asset preload' settings for your project. They probably also have a big influence on which assets are loaded upon start?
    So, to perhaps phrase it differently: what behavior were you expecting and why are you expecting that, what are you observing instead, and why do you think that the observed behavior is incorrect?
     
  3. MartinTilo

    MartinTilo

    Unity Technologies

    Joined:
    Aug 16, 2017
    Posts:
    2,431
    Sounds normal to me but just to clarify, from the code snippet you shared
    DestrObject() seems to destroy the GameObject that the LinksHolderOne is attached to and doesn't do anything about the instantiated prefab or the loaded ScriptableObject, so I feel like these should actually just be still in memory.

    I'm tempted to just chalk that up to a shortened snippet but that's what the gameObject variable implies for me.

    Also it sounds like you are trying to build an Addressable Assets system. Is there a reason why you aren't just using the Addressables package?
     
  4. MartinTilo

    MartinTilo

    Unity Technologies

    Joined:
    Aug 16, 2017
    Posts:
    2,431
    I read that as: An association made via direct assignment in the inspector. This uses the GUID system in the editor and builds into reference trees for builds. Loading in one Asset, loads in the entire tree of things it references to in this way.
     
  5. crol

    crol

    Joined:
    Aug 6, 2013
    Posts:
    30
    Thx for the reply.
    Just to clarify, in my game, there are game levels, each consist of like ~10 big prefabs, level blocks. Each block prefab contains meshes, game logic, lights, etc.
    And there are scriptable objects with level descriptions and arrays of these prefabs to instantiate when the level is selected.

    I was sure that if I store an array of these scriptable objects on the scene and then use one of them to create a mission (instantiate prefabs) everything would be ok. But what's happened is that all levels were already loaded in memory from the beginning because I have references to all scriptable objects and these scriptable objects have references to them. I can fix this just by loading scriptable objects by name from resources.

    And another problem is that if I change the scene or delete an object which stores an array of scriptable objects without directly assigning null to links all the prefabs gonna stay in memory no matter what. I am just interested in why is this working like that or how can I clean memory from objects which were automatically loaded in memory and there are no links to them in the scene already.

    But object with the link to scriptable object is destroyed, so there are no links to this scriptable object now and why it is still in memory (and also all the objects this scriptable object have references to)

    There are even no instantiated prefabs, just link to scriptable object (it can be selected directly in Inspector window or loaded by Resources.Load, doesn't matter)
     
    Last edited: Mar 5, 2021
  6. crol

    crol

    Joined:
    Aug 6, 2013
    Posts:
    30
    Much simpler example. Look at attached image.
    a) Prefab in project
    b) Scriptable Object in Project and it has link to Prefab from project
    c) GameObject in Scene with link to Scriptable Object (could be assigned in Inspector or loaded in Runtime by Resources.Load)
    When I Start this scene Scriptable Object and Prefab both automatically gonna be loaded to memory. If I destroy GameObject both Scriptable Object and Prefab gonna stay in memory. (Even after Resources.UnloadUnusedAssets();)
    How to release memory (unload Prefab at least)? Assign link to Scriptable Object to null and only then destroy GameObject. But why it is not automatic?
     

    Attached Files:

  7. MartinTilo

    MartinTilo

    Unity Technologies

    Joined:
    Aug 16, 2017
    Posts:
    2,431
    This is what we are calling a leaked managed shell object. I.e. Destroy only gets rid of the Native part of a UnityEngine.Object. The managed wrapper object you can use to refer to it from C# scripts is still there until garbage collection picks it up. It overloads the == operator, faking null, and implicit casts to bool return false. So depending on how you check your variable, it might appear as already gone, but it isn't, and that field still needs to be set to null, so that the GC can pick it up. The managed wrapper is also where your ScriptableObjects references and arrays live so that's why you're seeing this behavior.

    This is By Design.

    Even if, very confusing. We are working on an overhaul of the Memory Profiler package which at least highlights leaked shell Objects, so that it becomes easier to track and figure out what's going on.
     
    crol likes this.
  8. MartinTilo

    MartinTilo

    Unity Technologies

    Joined:
    Aug 16, 2017
    Posts:
    2,431
    And as I said, if you want to be able to assign references in the editor without them getting loaded into memory immediatly, get a more fine grained control over your Resources folder (which is essentially one big AssetBundle) or move away from using it, because of the issues it can cause (e.g. everything in it or referenced from within it ALWAYS gets into the build, used or not), then I think you really should look at using Addressables.
     
    crol and PraetorBlue like this.
  9. crol

    crol

    Joined:
    Aug 6, 2013
    Posts:
    30
    Yeah, thanx for your help)
     
    MartinTilo likes this.