98

I recently developed an html5 mobile application. The application was a single page where navigation hash change events replaced the entire DOM. One section of the application was a Google Map using API v3. Before the map div is removed from the DOM, I want to remove any event handlers/listeners and free up as much memory as possible as the user may not return to that section again.

What is the best way to destroy a map instance?

epignosisx
  • 6,152
  • 2
  • 30
  • 31
Chad Killingsworth
  • 14,360
  • 2
  • 34
  • 57
  • Related question (2014): https://stackoverflow.com/questions/21142483/google-maps-js-v3-detached-dom-tree-memory-leak – kashiraja Jan 07 '18 at 01:06
  • Code for attempting to remove all event listeners on a map, Google maps bug [35821412](https://issuetracker.google.com/issues/35821412) – kashiraja Jan 07 '18 at 01:11

7 Answers7

52

I'm adding a second answer on this question, because I don't want to remove the back and forth we had via follow-up comments on my previous answer.

But I recently came across some information that directly addresses your question and so I wanted to share. I don't know if you are aware of this, but during the Google Maps API Office Hours May 9 2012 Video, Chris Broadfoot and Luke Mahe from Google discussed this very question from stackoverflow. If you set the video playback to 12:50, that is the section where they discuss your question.

Essentially, they admit that it is a bug, but also add that they don't really support use cases that involve creating/destroying successive map instances. They strongly recommend creating a single instance of the map and reusing it in any scenario of this kind. They also talk about setting the map to null, and explicitly removing event listeners. You expressed concerns about the event listeners, I thought just setting the map to null would suffice, but it looks like your concerns are valid, because they mention event listeners specifically. They also recommended completely removing the DIV that holds the map as well.

At any rate, just wanted to pass this along and make sure it is included in the stackoverflow discussion and hope it helps you and others-

zzzzBov
  • 174,988
  • 54
  • 320
  • 367
Sean Mickey
  • 7,618
  • 2
  • 32
  • 58
32

The official answer is you don't. Map instances in a single page application should be reused and not destroyed then recreated.

For some single page applications, this may mean re-architecting the solution such that once a map is created it may be hidden or disconnected from the DOM, but it is never destroyed/recreated.

Bob Stein
  • 16,271
  • 10
  • 88
  • 101
Chad Killingsworth
  • 14,360
  • 2
  • 34
  • 57
  • 1
    This is very very bad — I have multilanguage single page application and i want to display Google Map on selected language. – artuska Nov 29 '18 at 14:21
  • 3
    It looks like this has been [fixed as of version 3.38.1](https://issuetracker.google.com/issues/35821412#comment65) (although I haven't independently verified). – Chrest Dec 15 '20 at 16:39
18

Since apparently you cannot really destroy map instances, a way to reduce this problem if

  • you need to show several maps at once on a website
  • the number of maps may change with user interaction
  • the maps need to be hidden and re-shown together with other components (ie they do not appear in a fixed position in the DOM)

is keeping a pool of map instances. The pool keeps tracks of instances being used, and when it is requested a new instance, it checks if any of the available map instances is free: if it is, it will return an existing one, if it is not, it will create a new map instance and return it, adding it to the pool. This way you will only have a maximum number of instances equal to the maximum number of maps you ever show simultaneously on screen. I'm using this code (it requires jQuery):

var mapInstancesPool = {
 pool: [],
 used: 0,
 getInstance: function(options){
    if(mapInstancesPool.used >= mapInstancesPool.pool.length){
        mapInstancesPool.used++;
        mapInstancesPool.pool.push (mapInstancesPool.createNewInstance(options));
    } else { 
        mapInstancesPool.used++;
    }
    return mapInstancesPool.pool[mapInstancesPool.used-1];
 },
 reset: function(){
    mapInstancesPool.used = 0;
 },
 createNewInstance: function(options){
    var div = $("<div></div>").addClass("myDivClassHereForStyling");
    var map =   new google.maps.Map(div[0], options);
    return {
        map: map,
        div: div
    }
 }
}

You pass it the starting map options (as per the second argument of google.maps.Map's constructor), and it returns both the map instance (on which you can call functions pertaining to google.maps.Map), and the container , which you can style using the class "myDivClassHereForStyling", and you can dinamically append to the DOM. If you need to reset the system, you can use mapInstancesPool.reset(). It will reset the counter to 0, while keeping all existing instances in the pool for reuse. In my application I needed to remove all maps at once and create a new set of maps, so there's no function to recycle a specific map instance: your mileage may vary. To remove the maps from the screen, I use jQuery's detach, which doesn't destroy the map's container .

By using this system, and using

google.maps.event.clearInstanceListeners(window);
google.maps.event.clearInstanceListeners(document);

and running

google.maps.event.clearInstanceListeners(divReference[0]);
divReference.detach()

(where divReference is the div's jQuery object returned from the Instance Pool) on every div I'm removing, I managed to keep Chrome's memory usage more or less stable, as opposed to it increasing every time I delete maps and add new ones.

Paolo Mioni
  • 1,008
  • 10
  • 17
5

I would have suggested removing the content of the map div and using delete on the variable holding the reference to the map, and probably explicitly deleteing any event listeners.

There is an acknowledged bug, though, and this may not work.

Andrew Leach
  • 12,945
  • 1
  • 40
  • 47
  • This is a good discussion. I don't think calling `delete` adds much (see http://stackoverflow.com/q/742623/1314132), but it really can't hurt. In the end, it comes down to this question: are there any references to the object? If yes, it won't be garbage collected. – Sean Mickey May 07 '12 at 17:24
  • 1
    @SeanMickey: which is where the bug becomes relevant. Version 2 has `GUnload()` to remove all the API's internal references. – Andrew Leach May 07 '12 at 17:28
  • I've been testing with this page in Chrome: http://people.missouristate.edu/chadkillingsworth/mapsexamples/memory.htm So far the memory usage after the map is removed drops only slightly, but is nowhere near the level before the map is instantiated. – Chad Killingsworth May 07 '12 at 17:45
  • @AndrewLeach Absolutely. But if they have a bug that's causing a memory leak, there isn't much we can do until it gets fixed. I mean, if making all of the map objects unreachable doesn't work, then `delete` isn't really a fix. They have to fix the big so that making the references unreachable works as it should or add a new function that provides the functionality you describe for `GUnload()`. – Sean Mickey May 07 '12 at 17:48
  • @ChadKillingsworth It sounds like the bug Andrew Leach mentions in his answer. It's similar to what is described by following the link he provided. In that case, not sure we can do anything but upvote the bug report to raise the priority on a fix. – Sean Mickey May 07 '12 at 17:51
  • I'm upvoting this answer, because it is better than mine. The basic approach of clearing the references should work, but I was unaware of the bug. This is more important than the approach. – Sean Mickey May 07 '12 at 17:53
  • Thanks for the feedback. I'm going to delay accepting an answer since it doesn't seem that there is much a user can do at the moment. Hopefully the bug will get fixed. – Chad Killingsworth May 07 '12 at 18:49
  • 1
    Chad/Andrew: yeah, I have reproduced this issue, unfortunately `delete` and clearing the `innerHTML` doesn't completely clear memory. Unfortunately it's not a high priority bug. – Chris Broadfoot May 09 '12 at 03:52
  • @Chris - thanks. It's a shame because my use case is mobile which was one of the original justifications for API v3. Also, further experiments show that clearing all the elements in the `body` doesn't free the memory either. – Chad Killingsworth May 23 '12 at 13:27
2

As google doesnt provide gunload() for api v3 better use iframe in html and assign map.html as a source to this iframe. after use make src as null. That will definitely free the memory consumed by map.

kumar
  • 21
  • 1
  • 2
1

When you remove the div, that removes the display panel and the map will disappear. To remove the map instance, just make sure that your reference to the map is set to null and that any references to other parts of the map are set to null. At that point, JavaScript garbage collection will take care of cleaning up, as described in: How does garbage collection work in JavaScript?.

Community
  • 1
  • 1
Sean Mickey
  • 7,618
  • 2
  • 32
  • 58
  • 1
    I'm not sure that setting the map variable to null will properly remove all the event listeners. – Chad Killingsworth May 07 '12 at 16:48
  • 1
    It's not just the map that has to be set to `null`, but any references to anything else. So if the marker reference is set to `null`, making it **unreachable**, there is no way to reach the event listener. It may still be connected to the map, but the map can't be reached, so it is just a big hunk of memory that has essentially become orphaned. It's the same as setting an `Array.length = 0`; if there are no other references to the members, they just form a group of orphaned memory that is eligible for garbage collection. – Sean Mickey May 07 '12 at 17:13
0

I guess you're talking about addEventListener. When you remove the DOM elements, some browsers leak these events and doesn't remove them. This is why jQuery does several things when removing an element:

  • It removes the events when it can using removeEventListener. That means it's keeping an array with the event listeners it added on this element.
  • It deletes the attributes about events (onclick, onblur, etc) using delete on the DOM element when addEventListener is not available (still, it has an array where it stores the events added).
  • It sets the element to null to avoid IE 6/7/8 memory leaks.
  • It then removes the element.
Florian Margaine
  • 58,730
  • 15
  • 91
  • 116
  • I'm mainly referring to the internal Google Maps API events. They can be added/removed/triggered using the API event methods documented at https://developers.google.com/maps/documentation/javascript/reference#event. While similar in functionality to the browser addEventListener, there are a large number of custom events specific to the map (such as "bounds_changed" and some of those event handlers hook into the browser events such as the map "resize" event. – Chad Killingsworth May 07 '12 at 17:17
  • Then keep an array of the events added and remove then manually using `removeEventListener` or `delete` depending on the kind of event. – Florian Margaine May 07 '12 at 17:19