Help understanding diff data from Memory Profiler

Can someone please help me understand some diff data from the Memory Profiler?

I have a GameSession class which only a single instance should exist during a session/match. I’m investigating memory leaks so I created a diff between 2 matches.

I expected to see 1 deleted GameSession instance and 1 new instance, or 1 same and 1 new if memory was leaking, but I have 1 delete and 2 new ones.

Please take a look at the screenshot GameSessionInstances.png. If the 2 new instances had similar data, I’d assume it’s something in my code creating 2 instances, but they are very different: they are both Manage Objects but one of them doesn’t have a Native Object Name, Native Instance Id. What does that mean?

I thought they could be the same instance but they have different addresses and different internal data. The deleted one is also different from the other 2.

Bonus question: what’s the <>c instance? Local variables for a lambda?

I made these measurements on an iPad Mini 2 but got similar results on the Editor.




The new one with m_CachedPtr = 0 is only the remaining managed shell object (it otherwise looks to be the same as the deleted) where the native object underneath has already been destroyed but you leaked the managed shell. It still has 11 references, hunt these down to fix your leak.

.<>c is autogenerated by the compiler. It could be a constructor of the class or yes a lambda.

From your other thread, I take it you’re on 2019.2. you might want to update to 2019.3 to get cleaner references as that version no longer reports indirect references but only direct ones.

1 Like

Hi @MartinTilo , thanks for helping me - as always :slight_smile:

So I assume that all different field values between that one and the deleted one is because they belong to different snapshots, so captured in different moments, is that correct?

That is correct yes

1 Like

@MartinTilo is there a way to find all empty shell objects that should be dead? Just search for Values that contain “m_CachedPtr=0”?

If so, sounds like a good idea for a feature: a button that tells me I probably have memory-leaking issues. I can’t think of a situation where it’s ok to have c++ objects destroyed but empty shell .NET objects living (not considering the ones that are about to get GCed).

By the way, you mentioned my other post ( Memory Profiler Diff Bug? ), I didn’t understand if I should upgrade to 2019.3 for that because it may be a reference misdirection problem or if you were giving me a general advice. Is it a bug?

I was thinking much the same. We generally can’t tell automatically if you have a leak because there’s usually a reason (reference) that’s keeping it in memory and without knowing what the current state should be, can’t infere if this is intentional or not. Though as long as we make clear that such a view only and specifically shows leaked managed shell objects, it should be possible to list these out.

(BTW, we we can also only guess at what objects would be about to be GCed by checking if they have a reference path to a valid root.)

The other thread is a bug, I’ve answered there too. And the update to 2019.3 would only help with tracking down those 11 references of your leaked shell mentioned in this thread, because they’d be cleaned from indirect references. It’s not necessary for either though. Snapshots are taken and analyzed way faster in 2019.3 though too so, I can only recommend going there from a (Memory) Profiler perspective :wink:

1 Like

@MartinTilo I want to build a tool that goes through all PackedMemorySnapshot.nativeObjects delegates to search for objects with nativeObjectAddress == 0. This will help me find code that programmers forgot to unsubscribe from events/delegates.

I can’t find a way to get references to/from PackedNativeUnityEngineObject, though, let alone delegates. I saw the “delegates” field in the Name column of an object in the Memory Profiler window, that’s what I want. Is there a way to get it?

One more thing: still on understanding the diff data, I noticed something on this new screenshot: the empty shell object on this case has a different InstanceID than the one from the old snapshot. This didn’t happen in the screenshot on my first post.

Investigating memory leak due to event subscription, when I took the second snapshot I printed the instance id of the subscribers of the event that this class is subscribing to and I can confirm that the id of the old/deleted object still subscribed to it is -140078 and the new instance is -308824. So why does the empty shell has id 206374? It doesn’t matter much, I’m just curious.

uh wait. Native Objects don’t have delegates or fields… at least not in a way that would be captured by the memory profiler. The data that the Memory Profiler window shows you is not in the snapshot fields just like that to query it. The Memory Profiler package first crawls the snapshot to generate the data structure for these tables.

I.e. You would need to go through all PackedMemorySnapshot.gcHandles and treat the target uints as pointers into PackedMemorySnapshot.managedHeapSections.bytes, (obviously taking the sections startAddress into account).
The first pointer in the bytes points to the TypeDescription.typeInfoAddress which you’d have to search for in the PackedMemorySnapshot.typeDescriptions and then you can use the TypeDescription.fields to look at the FieldDesctription to find fields by name e.g. or use the FieldDescription.typeIndex to look for the type of it in PackedMemorySnapshot.typeDescriptions and see if it is a delegate.

then you can use the type and field descriptions to find that instance ID and check the value.

Yes. This snapshot format and API is a pain to work with. We’re very well aware. We have to work with this. We’re working on both making the format and API nicer as well as improving the filed reader and crawler and exposing all this in a nicer way to everyone but this stuff sadly takes some time and care :wink:

If you’re daring, you could also modify the source of the memory profiler package. Not sure if that’s gonna get you there faster though. At least if you’d try to make sense of the code for the tables. But you could maybe use the database APIs.

1 Like

Are those snapshots from the same session? Instance IDs are not consistent over sessions.

Also I believe negative instance id’s usually refer to assets (and/or native objects) and positive ones to non-assets (and/or managed ones) but I would only use this assumption as an indication, not solid proof of anything. Scratch that, I flipped the sign and mangled the association, @Peter77 's post below has it right.

1 Like

According to my tests, that’s not correct. It should be:

PackedNativeUnityEngineObject.isPersistent == true // asset
PackedNativeUnityEngineObject.instanceId < 0 // object created at runtime
PackedNativeUnityEngineObject.instanceId >= 0 // object stored in scene

https://github.com/pschraut/UnityHeapExplorer/blob/master/Editor/Scripts/HeEditorGUI.cs#L351

2 Likes

Whoops, you’re right @Peter77 , should’ve check the source code and double checked the sign thing. What you should not be basing your assumption on was that a negative ID means no association with a file/asset, as that association might happen at runtime and after a negative ID was assigned because the object was created at runtime.

So, back to

because the first object which has it’s managed shell leaked was loaded into memory as part of the scene, while the others where created at runtime.

3 Likes