Skip to content

Latest commit

 

History

History
344 lines (186 loc) · 12.7 KB

GC-IE.md

File metadata and controls

344 lines (186 loc) · 12.7 KB

What is memory leak?

Memory that is not required by an application anymore that for some reason is not returned to the operating system or the pool of free memory

Quote from Douglas Crockford:

Microsoft's Internet Explorer contains a number of leaks, the worst of which is an interaction with JScript. When a DOM object contains a reference to a JavaScript object (such an event handling function), and when that JavaScript object contains a reference to that DOM object, then a cyclic structure is formed. This is not in itself a problem. At such time as there are no other references to the DOM object and the event handler, then the garbage collector (an automatic memory resource manager) will reclaim them both, allowing their space to be reallocated. The JavaScript garbage collector understands about cycles and is not confused by them. Unfortunately, IE's DOM is not managed by JScript. It has its own memory manager that does not understand about cycles and so gets very confused. As a result, when cycles occur, memory reclamation does not occur. The memory that is not reclaimed is said to have leaked. Over time, this can result in memory starvation. In a memory space full of used cells, the browser starves to death.


Background:

JScript and VBScript both are automatic storage languages. Unlike, say, C++, the script developer does not have to worry about explicitly allocating and freeing each chunk of memory used by the program. The internal device in the engine which takes care of this task for the developer is called the garbage collector. This is very similar to Java Garbage Collection (GC):

Java GC is explained here

JScript uses a nongenerational mark-and-sweep garbage collector. It works like this:

Scav list:

Every variable which is "in scope" is called a "scavenger".

A scavenger may refer to a number, an object, a string, whatever. JScript engine maintains a list of scavengers -- variables are moved on to the scav list when they come into scope and off the scav list when they go out of scope.

Every now and then the garbage collector(GC) runs.

First it puts a "mark" on every object, variable, string, etc – all the memory tracked by the GC.

JScript uses the VARIANT data structure internally and there are plenty of extra unused bits in that structure, so we just set one of them.

Engine clears the mark on the scavengers and the transitive closure of scavenger references. So if a scavenger object references a nonscavenger object then we clear the bits on the nonscavenger, and on everything that it refers to.

At this point we know that all the memory still marked is allocated memory which cannot be reached by any path from any in-scope variable. All of those objects are instructed to tear themselves down, which destroys any circular references.

Manually forcing GC:

You can force the JScript garbage collector to run with the CollectGarbage() method,

Doc for this JScript method: CollectGarbage How check the avaiabilty of undocumented CollectGarbage():

check Collect Garbage

Leaks:

When here is a reference of any kind created (such as an event handler) between the two engines, and let us say one of your Javascript function scope exits, there may still be a reference to a symbol within that function that is referenced in the DOM (e.g. via an event handler such as onload). This in turn confuses JScript garbage collector, which in turn causes for it not to clean up, which then in turn causes the memory leak.

How to Help GC: We can help the garbage collector in JScript by marking the symbols with a null value that you want to be garbage collected.

Example to write leak free code:


 function createPerson(firstName, lastName) {
   var date = new Date(), counter = 0, registry = {}, output;

   // function code work here

   // cleanup time
   // locals cleanup after we produced output
   // The strategy is to dispose of as many symbols as possible

   date = counter = registry = null;

   // the return statement cleans up whatever symbol it returns 
   return output;
  }



DOM references

This is case of we are crossing from one engine into the other:


function getElements(id) {
   // DOM method: getElementById(...). We are crossing engines

   var element = document.getElementById(id);
   var  i ;  
   // get all the div tags of tagLength in size 
   var  tags = document.getElementsByTagName("div"); 
   var tagsLength = tags.length;

   //  clear all references that you hold to a DOM element
   //  this is the main source for the memory leaks
   for (i = 0; i < tagsLength; i++) {
     // code goes here
     
     // help GC 
     tags[i] = null;
    }

   // help GC: clean up other locals
   element = i = tags = null;
}

How about Event Handlers:

Event handler is a link between the DOM engine and the JavaScript engine since the element references a JavaScript function

Solution:

maintain a registry of all event handlers (say: eventRegistry) that you've attached so that you can eventually detach them in window.unload()

//Pay particular attention to event subscription and DOM references, 
// and ensuring that each component has a working teardown operation that releases all DOM references. 

window.unload = function () {
  // clean up event by setting event registry items to null 

  // self
  window.unload = null;

   // force GC -  IE-specific function
   // it does not guarantee that it will be done on demand.
   //  we are merely asking for it to be done sooner than later.
   // check this method is not undefined
   if (window.CollectGarbage() !== undefined){
       window.CollectGarbage();
   }

 }


Observations Single Page Applications (SPA):

An IE process functions normally until about 1.6GB RAM.

Somewhere between 1.6GB and 1.8GB the browser will recycle the process, which is almost transparent to the user, dropping RAM back down to 200MB.

The process can only be transparently recycled when the user navigates from one page to another or reloads the page (which may never happen in an SPA).

If the web app causes IE to consume RAM in a single operation so that it starts at < 1.6GB and completes at > 1.8GB the process will terminate without warning.

About IE 6 and 7

Internet Explorer 6 and 7 are known to have reference-counting garbage collectors for DOM objects. Cycles are a common mistake that can generate memory leaks The following circular reference code will result in memory leak:

var div;
window.onload = function() {
  div = document.getElementById('myDivElement');
  // main issue is in the next line...
  div.circularReference = div;
  div.lotsOfData = new Array(10000).join('*');
};

DOM element "myDivElement" has a circular reference to itself in the "circularReference" property. If the property is not explicitly removed or nulled, a reference-counting garbage collector will always have at least one reference intact and will keep the DOM element in memory even if it was removed from the DOM tree. If the DOM element holds lots of data (illustrated in the above example with the "lotsOfData" property), the memory consumed by this data will never be released.

Tools

IE JS Leaks Detector

This JavaScript Memory Leak Detector is a debugging tool to detect memory leaks and enforce best practices in JavaScript code when working with version of Internet Explorer older than IE8.

IEJSLeaksDetector is a plain, native, Windows application and does not require any particular setup (the executable can be just unzipped and run). It only runs in 32 bit versions of Windows. 64 bits editions are not supported yet.

IEJSLeaksDetector

How to use the tool

The user can start the memory profiling of a web application navigating to the desired URL. A new tab is opened with a WebBrowser control and a tree view shows all the documents and scripts that compose the current page. When the user has finished to interact with the page he can click the "Stop" button, which causes the tool to close the control and track possible leaks. Memory leaks are listed specifying the DOM object's type and a list of "attached" JavaScript objects whose circular reference could be the cause of the leak. The tool also shows the call stack correponding to a memory leak, which represents the state of the script at the moment when the JavaScript object was attached to the DOM object. Finally, a script window highlights the exact point in the JavaScript code where the memory leak originated.

More Details


Tips

  1. How to prevent accidental globals in your code:
'use strict';

at the beginning of your JavaScript files. This enables a stricter mode of parsing JavaScript that prevents accidental globals.

Note:

The Global variables are by definition noncollectable (unless nulled or reassigned).

In particular, global variables used to temporarily store and process big amounts of information are of concern.

If you must use a global variable to store lots of data, make sure to null it or reassign it after you are done with it.

One common cause for increased memory consumption in connection with globals are caches.

Caches store data that is repeatedly used. For this to be efficient, caches must have an upper bound for its size.

Caches that grow unbounded can result in high memory consumption because their contents cannot be collected.


  1. Forgotten callbacks
var element = document.getElementById('button');

function onClick(event) {
    element.innerHtml = 'text';
}

element.addEventListener('click', onClick);
// Do stuff
element.removeEventListener('click', onClick);
element.parentNode.removeChild(element);
// Now when element goes out of scope,
// both element and onClick will be collected even in old browsers that don't
// handle cycles well.

  1. Out of DOM references
// Here two references to the same DOM element are kept: 
//   one in the DOM tree and the other in the dictionary

// dictionary
var elements = {
    button: document.getElementById('button'),
    image: document.getElementById('image'),
    text: document.getElementById('text')
};

function doStuff() {
    image.src = 'http://some.url/image';
    button.click();
    console.log(text.innerHTML);
    // Much more logic
}

function removeButton() {
    // The button is a direct child of body.
    document.body.removeChild(document.getElementById('button'));
    // DOM tree item gone but:

    // At this point, we still have a reference to #button in the global
    // elements dictionary. In other words, the button element is still in
    // memory and cannot be collected by the GC.
}
  1. closure memory leak - anonymous functions that capture variables from parent scopes

Refer: Here


Useful links

How Do The Script Garbage Collectors Work?

JavaScript Memory Leaks in Internet Explorer

Understanding and Solving Internet Explorer Leak Patterns

How to Work Around IE Memory Leaks in SPAs

IE Memory leak in emberjs

MS Virtual machines

GC for game developers

Memory Management in Javascript

4 Types of Memory Leaks in JavaScript and How to Get Rid Of Them

An interesting kind of JavaScript memory leak

JScript Memory Leaks

Chrome V8 engine specific

Watch the memory grow

Watching the GC work

Scattered objects

Detached nodes

Memory and V8 hidden classes