RealityKit / visionOS – Memory not released after dismissing ImmersiveSpace with USDZ models

Hi everyone,

I’m encountering a memory overflow issue in my visionOS app and I’d like to confirm if this is expected behavior or if I’m missing something in cleanup.

App Context

  • The app showcases apartments in real scale using AR.
  • Apartments are heavy USDZ models (hundreds of thousands of triangles, high-resolution textures).
  • Users can walk inside the apartments, and performance is good even close to hardware limits.

Flow

  • The app starts in a full immersive space (RealityView) for selecting the apartment.
  • When an apartment is selected, a new ImmersiveSpace opens and the apartment scene loads.
  • The scene includes multiple USDZ models, EnvironmentResources, and dynamic textures for skyboxes.
  • When the user dismisses the experience, we attempt cleanup:
    • Nulling out all entity references.
    • Removing ModelComponents.
    • Clearing cached textures and skyboxes.
    • Forcing dictionaries/collections to empty.

Despite this cleanup, memory usage remains very high.

Problem

After dismissing the ImmersiveSpace, memory does not return to baseline. Check the attached screenshot of the profiling made using Instruments:

  1. Initial state: ~30MB (main menu).
  2. After loading models sequentially: ~3.3GB.
  3. Skybox textures bring it near ~4GB.
  4. After dismissing the experience (at ~01:00 mark): memory only drops slightly (to ~2.66GB).
  5. When loading the second apartment, memory continues to increase until ~5GB, at which point the app crashes due to memory pressure.

The issue is consistently visible under VM: IOSurface in Instruments. No leaks are detected.

So it looks like RealityKit (or lower-level frameworks) keeps caching meshes and textures, and does not free them when RealityView is ended. But for my use case, these resources should be fully released once the ImmersiveSpace is dismissed, since new apartments will load entirely different models and textures.

Cleanup Code Example

Here’s a simplified version of the cleanup I’m doing:

func clearAllRoomEntities() {
    for (entityName, entity) in entityFromMarker {
        entity.removeFromParent()

        if let modelEntity = entity as? ModelEntity {
            modelEntity.components.removeAll()
            modelEntity.children.forEach { $0.removeFromParent() }
            modelEntity.clearTexturesAndMaterials()
        }

        entityFromMarker[entityName] = nil
        removeSkyboxPortals(from: entityName)
    }
    entityFromMarker.removeAll()
}
extension ModelEntity {
    func clearTexturesAndMaterials() {
        guard var modelComponent = self.model else { return }

        for index in modelComponent.materials.indices {
            removeTextures(from: &modelComponent.materials[index])
        }

        modelComponent.materials.removeAll()
        self.model = modelComponent
        self.model = nil
    }

    private func removeTextures(from material: inout any Material) {
        if var pbr = material as? PhysicallyBasedMaterial {
            pbr.baseColor.texture = nil
            pbr.emissiveColor.texture = nil
            pbr.metallic.texture = nil
            pbr.roughness.texture = nil
            pbr.normal.texture = nil
            pbr.ambientOcclusion.texture = nil
            pbr.clearcoat.texture = nil
            material = pbr
        } else if var simple = material as? SimpleMaterial {
            simple.color.texture = nil
            material = simple
        }
    }
}

Questions

  1. Is this expected RealityKit behavior (textures/meshes cached internally)?
  2. Is there a way to force RealityKit to release GPU resources tied to USDZ models when they’re no longer used?
  3. Should dismissing the ImmersiveSpace automatically free those IOSurfaces, or do I need to handle this differently?
  4. Any guidance, best practices, or confirmation would be hugely appreciated.

Thanks in advance!

Answered by giovanniR2U in 856613022

Thanks to @Vision Pro Engineer answer, I could find the solution!

I tried checking for others references to the entities added in the ImmersiveSpace. Indeed, I could find some references and auxiliary entities that were not being explicitly removed from parent on dismiss. Also, I made sure to remove the rootEntity from the parent as I was not doing it before (I thought that removing the entities themselves would be enough).

It worked! Now the memory almost goes back to the state before the ImmersiveSpace was opened.

However, I did had to:

  • force the .removeFromParent() for each non-nullable entities (MOST RELEVANT) ;
  • reset the arrays and dictionary referencing entities to [] or [:];
  • nulling every reference for Textures, EnvironmentResources and Colliders etc (LESS RELEVANT);

I think that this should not be the expected behavior. I guess that most developers would assume that this clean up is supposed to be automatically made when the ImmersiveSpace is dismissed and/or when the RealityView is dismissed.

But thanks a lot for the help!

Hello @giovanniR2U , thank you for your question!

Swift uses automatic reference counting (ARC), which means memory is automatically cleaned up for any object that is no longer referenced. Often, this is as simple as removing a root entity from the scene (thereby removing each of its descendants) or dismissing the ImmersiveScene entirely. Going through each entity in the scene and calling removeFromParent() for every entity and setting texture references to nil should not be necessary. The important thing is to verify there are no more references in memory, so make sure you don't have any variables lying around that might still be pointing to the objects you want to unload.

When you load your textures sequentially, are you keeping a reference to these textures in the system that loads them? This would prevent ARC from unloading those textures. You mention your app goes from 30 MB to 3.3 GB after loading just the textures, and then only goes to 2.6 GB after "dismissing the experience." This to me suggests that it is mostly textures that are still in memory, so I would recommend investigating the system loading them in the first place.

What you are seeing is not expected behavior, so in case you are seeing a memory leak with RealityKit itself, I recommend submitting feedback using Feedback Assistant and then sharing the ticket number here. It would be very helpful if you are able to share a project that reproduces this issue to help us track this on our end.

Let me know if you have more questions! I'm happy to help get to the bottom of this issue. Thank you!

Hello again @giovanniR2U .

Additionally, I recommend upgrading to visionOS 26 if you have not already. There are a number of new features and bug fixes related to memory management that may be relevant. Let me know if you have any questions!

Accepted Answer

Thanks to @Vision Pro Engineer answer, I could find the solution!

I tried checking for others references to the entities added in the ImmersiveSpace. Indeed, I could find some references and auxiliary entities that were not being explicitly removed from parent on dismiss. Also, I made sure to remove the rootEntity from the parent as I was not doing it before (I thought that removing the entities themselves would be enough).

It worked! Now the memory almost goes back to the state before the ImmersiveSpace was opened.

However, I did had to:

  • force the .removeFromParent() for each non-nullable entities (MOST RELEVANT) ;
  • reset the arrays and dictionary referencing entities to [] or [:];
  • nulling every reference for Textures, EnvironmentResources and Colliders etc (LESS RELEVANT);

I think that this should not be the expected behavior. I guess that most developers would assume that this clean up is supposed to be automatically made when the ImmersiveSpace is dismissed and/or when the RealityView is dismissed.

But thanks a lot for the help!

RealityKit / visionOS – Memory not released after dismissing ImmersiveSpace with USDZ models
 
 
Q