0

I am currently trying to find some potential memory leaks in my angular application and found something which should regard to the hereMap, that I am using in one component. This is the situation:

I have an Angular 12 SPA with two components:

ComponentA - completely empty angular component - just for routing away from component B

ComponentB - the component that is using the hereMap.

When switching routes from Component A to B to A I would expect the garbage collector to remove most of the allocated memory after going back to A after a certain amount of time or when clicking "Collect garbage" in DevTools.

Here is what drives me crazy:

Every time when I go to the route with ComponentB, it seems like mapsjs-core.js adds a new TileManager that stays in memory forever and holds an enormous amount of objects and Arrays (3.5k Arrays and 10k Objects each time) which adds up to like 3-5mb memory each time.

Heap Snapshot

Those objects include textures, meshes, shields, etc in multiple instances of TileManagers (TileManager_0, TileManager_1, TileManager_2 after 3 times of creating a new instance of ComponentB).

After ngOnDestroy of ComponentB got called, ComponentB is no longer part of the memory, so disposing the map seems to work as expected.

Here is how the components look like:

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-component-a',
  templateUrl: './component-a.component.html',
  styleUrls: ['./component-a.component.styl']
})
export class ComponentAComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
  }

}

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-component-b',
  templateUrl: './component-b.component.html',
  styleUrls: ['./component-b.component.styl']
})
export class ComponentBComponent implements OnInit, AfterViewInit, OnDestroy {
  @ViewChild('map') public mapElement: ElementRef;
  private map: H.Map;
  private platform: H.service.Platform;
  constructor() { }

    ngOnInit(): void {
        this.platform = new H.service.Platform({
          apikey: myKey
      });
    }
  
    ngAfterViewInit() {
        const defaultLayers = this.platform.createDefaultLayers();
        // Set min and max zoom level
        defaultLayers.vector.normal.map.setMax(16);
        defaultLayers.vector.normal.map.setMin(2);

        // Initialize the map
        this.map = new H.Map(
            this.mapElement.nativeElement,
            defaultLayers.vector.normal.map,
            {
                zoom: ZoomLevels.ZOOM_MIN_SINGLE_MAP
            }
        );
    }
    
    public ngOnDestroy(): void {
        this.map.dispose();
    }

}
  • I don’t see you import H..? So it probably is just an object on window? If that is the case, it has nothing to do with Angular (or GC in general). It is just an application loaded with a script tag that lives on window scope..? – MikeOne Jun 16 '21 at 16:31
  • You are right. As mentioned here: https://developer.here.com/documentation/maps/3.1.25.0/dev_guide/topics/get-started.html . It is just a loaded script and I added the package https://www.npmjs.com/package/@types/heremaps for type definitions – Robert Pabst Jun 16 '21 at 17:37
  • But I would have expected the this.map.dispose() to release all objects that were created by this map-context. I also thought, that it might be a problem of recreating H.service.Platform with each ComponentB but even setting the platform to a static variable and checking for ComponentB.platform in the constructor didn't help – Robert Pabst Jun 16 '21 at 17:46
  • It’s probably using some internal caching and it might release some stuff on a dispose (which might later be collected). I don’t think it is an issue.. – MikeOne Jun 16 '21 at 18:01
  • Would be nice but somehow I don't think that this is just a "feature" for caching. It keeps every scene and view every created in memory. After changing routes 13 times, memory rises from 30mb to 105mb. Also found "Detached WebGLRenderingContext" 13 times – Robert Pabst Jun 16 '21 at 18:27
  • Could you please share which version HERE JS API you are using? –  Jun 22 '21 at 07:25
  • Thanks for the information. We will check and let you know. –  Jun 24 '21 at 07:54
  • Thank you. This also seems to happen with older versions. I also gave it a try with a completely clean angular project to exclude any side-effects from other libraries but the same thing happens again. If you need any help in reproducing this, I am happy to help you – Robert Pabst Jun 24 '21 at 14:17
  • one question. After the ngOnDestroy is called ,are the here map context object are not released? –  Jun 25 '21 at 03:16
  • Yes, to me it looks like something is not releaesed as supposed and thats why everything stays in memory. If you can send me some kind of a contact, we could speed up communication a bit and I could send you a heapsnapshot or the example project I created. From what I can see from the heap snapshot, it all comes done to the TileManager. Everytime I open the map, there is a new instance of it, holding thousands of objects that won't get released. Along with all the classes called o, l, Sh, e, p, Op and so on. Feel free to contact me by email: robert.pabst.stackoverflow@gmail.com – Robert Pabst Jun 25 '21 at 06:22
  • Thank you for the details I am checking with R&D team, with the above sample code shared by you. If they need more information I will let you know. –  Jun 25 '21 at 09:18
  • Could you please try with below example and check if there is same issue in the example. https://developer.here.com/blog/display-here-maps-angular-web-application –  Jun 25 '21 at 11:42
  • Hi, the same thing happens with the example from the blog. Plus I think the example misses disposing of any HereMaps-Content which would blow up the memory and performance of the current tab even more since the Javascript VM Instances (2 for each new Map-Instance) will stay in memory for the rest of the runtime of your application – Robert Pabst Jun 27 '21 at 14:07
  • could you please share heap snapshot of those HERE map objects. And also ,which browser and version you are using for testing? –  Jun 29 '21 at 04:02
  • You can download the zipped heapsnapshot here: https://file.io/AyLDegdPAOvW . I am using Chrome on Version 79.0.3945.88 (64-Bit) on a MacBook Pro (13-inch, 2019, Two Thunderbolt 3 ports) with macOS Big Sur11.4 (20F71) – Robert Pabst Jun 29 '21 at 08:15

1 Answers1

0

We did performance testing on both simple vanilla Javascript application and angular application. And the object are getting released from the heap memory as required. Please find the below attached results.

enter image description here

And please keep note of the point that the Map is not supposed to dispose any other objects but itself, so application must explicitly dispose any other resource created by the application. The reason for it is very simple: let's take an application, that created a provider and populated it, the application might decide to re-use this provider, but dispose the map. And please follow below links for best practices.

1>Working of HERE Maps API for Javascript. https://developer.here.com/documentation/maps/3.1.25.0/dev_guide/topics/get-started.html

2>Angular example.

https://developer.here.com/blog/display-here-maps-angular-web-application(please add disposing of heremap object in the ngOnDestroy method of angular i.e this.map.dispose())

3>Best Practices.

https://developer.here.com/documentation/maps/3.1.25.0/dev_guide/topics/best-practices.html

  • The graph looks the same on my project but still the TileManagers which get created by the mapsjs-core.js are kept in memory for every instance of the map that I create, when opening the component-route. I'm not sure how it gets created but I'm not doing this inside my code as far as I can see. Please correct me if I'm wrong about that. I think the creation of the tileManager happens when creating a new Map-Instance in the ngAfterViewInit. If there is any chance in disposing the provider or even re-using it by passing it to the new map, please let me know about it. – Robert Pabst Jun 30 '21 at 10:45
  • Yes, tileManager will be created when Map-Instance is created .And same will be disposed on calling the dispose method. We tried with the above angular example and worked fine . There might be some difference in implementation or chrome GC issue. Could you please put one extra check in your application to create the Map-Instance only if it null Sample code: if(null==this.map) { this.map = new H.Map() ); –  Jul 01 '21 at 06:54
  • Thanks for your answers! My solution for now will be what you suggested in your last comment. I'm going to reuse the map-instance by setting it as a static variable inside my map-component. By this, my custom components will get disposed correctly and I don't have to worry about any memory-issues. Only thing I have to remember, is to remove all map-objects from the map when disposing the component so that the GC can remove them. Unluckily neither me nor the GC could make the map dispose the TileManager correctly but making it static, works for me. Thanks for your support! – Robert Pabst Jul 05 '21 at 07:17