Memory leak in JavaScript

What is a memory leak?

The running of the program requires memory. As long as the program asks, the operating system or runtime must supply memory.

For a continuously running service process (daemon), the memory that is no longer used must be released in a timely manner. Otherwise, the memory usage will become higher and higher, which would affects system performance, even crash the process.

Memory leak is used to describe the failure to release unreachable memory, which can no longer be allocated again by any process during execution of the allocating process.

Some languages, such as the C, must manually free up memory, and the programmer is responsible for memory management.

char * buffer;
buffer = (char*) malloc(42);

// Do something with buffer

free(buffer);

In code above, the malloc method is used for applying for memory, you must use the free method to release memory after it is no longer used.

This is cumbersome, so most languages ​​provide automatic memory management to reduce the burden on programmers, which is called “garbage collection” (garbage collector).

The garbage collection mechanism

How does the garbage collection know which part of memory is no longer needed?

The most commonly used method is called “reference counting“: The language engine has a “reference table” that holds the number of references to all resources in memory (usually various values). If the number of references to an object is 0, it means that the object is no longer used, so you can release the memory of this object.

In the figure above, two objects in the lower left corner can be released because there is no reference to them.
If a objects is no longer needed but the number of references is not 0, so the garbage collector can not release the memory, which is memory leak.

const arr = [1, 2, 3, 4];
console.log('hello world');

In the code above, the array [1, 2, 3, 4] is a an object that consumes memory. Variable arr is the only reference to this object, so the number of references is 1. Although the following code does not use arr, it continues to consume memory.

If you add a line of code to remove the reference to [1, 2, 3, 4] from the arr, this memory can be freed by the garbage collector.

let arr = [1, 2, 3, 4];
console.log('hello world');
arr = null;

In the code above, the arr is reset to null so that the reference to [1, 2, 3, 4] is lifted, and the number of references becomes 0 so that memory can be released.

So it’s not that there’s a garbage collection mechanism that makes it easier for programmers. You still need to focus on memory consumption: those that take up space, and once they are no longer used, you have to check if there are references to them. If so, you must manually dismiss the reference.

Memory leak Identification methodMemory leak Identification method

How can I observe a memory leak?
The rule of thumb is that if the memory is consumed once more than once after five successive garbage collections, there is a memory leak. This requires real-time viewing of memory usage.

browserbrowser

Follow these steps to view memory footprint in Chrome.

– Open Developer tools, select Performance Panel
– Check memory in the Capture field at the top
– Click on the Recording button in the upper left corner.
– Perform various operations on the page to simulate user usage.
– After a while, click on the Stop button in the dialog box to display the memory footprint of the time.

If the memory footprint is basically stable, close to the average level, there is no memory leak.

On the contrary, there is memory leak.

Node.js

We can use process.memoryUsage to measure the memory usage of the Node.js process

For example, the code:

console.log(process.memoryUsage());

Will generate:

{
  rss: 4935680,
  heapTotal: 1826816,
  heapUsed: 650472,
  external: 49879
}

process.memoryUsage returns an object that contains memory usage information for the node process. The object contains four fields in bytes, and have the following meanings.

  • RSS (Resident set size): All memory consumption, including instruction area and stack.
  • heapTotal: Total size of the heap.
  • heapUsed: Heap actually used.
  • external: Memory consumed by C + + objects inside the External V8 engine.

Use the heapUsed field to determine if there is a memory leak.

WeakMap

As mentioned earlier, it is very important to clear the reference in time. But you can’t always remember to release the memory manually, sometimes forgetting it by mistake, so there are so many memory leaks.

It is best to have a way to declare which references must be purged manually and which references can be ignored when you create a new reference. When other references disappear, the garbage collection mechanism can release memory. This will greatly reduce the burden of the programmer, you just need to clear the main reference.

ES6 with this in mind, two new data structures have been introduced: WeakSet and WeakMap. Their references to values are not included in the garbage collection mechanism, so there is a “weak” in the name, which means that this is a weak reference.

Take Weakmap as an example to see how it solves the memory leak.

const wm = new WeakMap();

const element = document.getElementById('example');

wm.set(element, 'some information');
wm.get(element) // "some information"

In the code above, a new Weakmap instance is created, and then a DOM node is stored as a key in the instance, and some additional information is used as the key value in the Weakmap. At this point, the reference to the element in Weakmap is a weak reference and will not be counted into the garbage collection mechanism.

In other words, the reference count for the DOM node object is 1, not 2. At this point, once the reference to the node is eliminated, the memory it consumes will be freed by the garbage collection mechanism. This key-value pair saved by Weakmap will also disappear automatically.
Basically, you can use Weakmap if you want to add data to an object without interfering with the garbage collection mechanism.

WeakMap example

First, open the node command line.

node --expose-gc

The --expose-gc parameter indicates that garbage collection is allowed to be performed manually.

Then execute the following code.

// Perform a garbage collection manually and ensure that the memory usage is accurate
> global.gc(); 
undefined

// View the initial state of memory consumption, heapUsed is around 4M
> process.memoryUsage(); 
{ rss: 21106688,
  heapTotal: 7376896,
  heapUsed: 4153936,
  external: 9059 }

> let wm = new WeakMap();
undefined

> let b = new Object();
undefined

> global.gc();
undefined

// heapUsed is still around 4M
> process.memoryUsage(); 
{ rss: 20537344,
  heapTotal: 9474048,
  heapUsed: 3967272,
  external: 8993 }

// add a new key-value pair in WeakMap
> wm.set(b, new Array(5*1024*1024));
WeakMap {}

// Perform a garbage collection manually
> global.gc();
undefined

// heapUsed is now around 45M
> process.memoryUsage(); 
{ rss: 62652416,
  heapTotal: 51437568,
  heapUsed: 45911664,
  external: 8951 }

// dereference object b
> b = null;
null

// perform garbage collection again
> global.gc();
undefined

// Now the heapUsed has turned back to about 4M.
> process.memoryUsage(); 
{ rss: 20639744,
  heapTotal: 8425472,
  heapUsed: 3979792,
  external: 8956 }

In the above code, as long as the external reference disappears, the WeakMap internal reference is automatically garbage collected. This shows that with its help, to eliminate the memory leaks will be much simpler.

JavaScript and finite-state machine

Finite-state machine is a very useful model that can simulate most of the things in the world.

Simply put, it has three characteristics:

  • The number of the state is limited.
  • At any moment, a state machine can only in one state.
  • Under certain conditions, a state machine can transit from one state to another.

Its meaning for JavaScript is that many objects can be written as finite state machines.

For example, the page has a menu element. When the mouse hover, the menu is displayed; when the mouse is removed, the menu is hidden. If you use a finite state machine description, this menu is only two states (show and hide), the mouse will lead to state changes.
The code can be written as follows:

const menu = {
  currentState: 'hide',

  initialize: function() {
    var self = this;
    self.on("hover", self.transition);
  },

  transition: function(event){
    switch(this.currentState) {
      case "hide":
        this.currentState = 'show';
        doSomething();
        break;
      case "show":
        this.currentState = 'hide';
        doSomething();
        break;
      default:
        console.log('Invalid State!');
        break;
    }
  }
}

You can see that the simple implementation of a finite-state machine is logically clear and very expressive, which is kind of suitable for encapsulating events. The more states and events an object has, the more suitable for using finite-state machine.

In addition, JavaScript is an language with a lot of asynchronous operations, a common usage is to use callback functions, but this will cause confusing code structure, known as “callback hell” – which is difficult to test and debug. The finite state machine provides a better way to hook the asynchronous operation to the state change of the object, and when the asynchronous operation is finished, a corresponding state change occurs, which in turn triggers other operations. This is more logically clear and easier to reduce the complexity of the code than using callbacks, event monitoring, publish / subscribe, and others.

The following describes a finite state machine function library Javascript Finite State Machine . This is a powerful, easy to understand finite-state machine library in javascript.
The library provides a global object StateMachine, using the object’s create method, you can generate a finite state machine instance.

const fsm = StateMachine.create();

When generating, you need to provide a parameter object that describes the nature of the instance. For example, traffic lights can be described as follows:

const fsm = StateMachine.create({
  initial: 'green',
  events: [
    {name: 'warn', from: 'green', to: 'yellow'},
    {name: 'stop', from: 'yellow', to: 'red'},
    {name: 'ready', from: 'red', to: 'yellow'},
    {name: 'go', from: 'yellow', to: 'green'}
  ]
});

The initial status of the traffic signal is green, the events attribute is the collection of events that trigger a state change, such as the warn event causes the green state to be yellow, and the stop event causes the yellow state to become red and so on.

After generating the instance, you can query the current status at any time.

  • fsm.current: returns the current state.
  • fsm.is (s): Returns a boolean indicating whether state s is the current state.
  • fsm.can (e): Returns a boolean indicating whether event e can be triggered in the current state.
  • fsm.cannot (e): Returns a boolean indicating whether event e can not be triggered in the current state.

Javascript Finite State Machine allows two callback functions to be specified for each event, with the warn event as an example:

  • onbeforewarn : Triggered before the warn event occurs.
  • onafterwarn (can be abbreviated as onwarn): Triggered after the warn event occurs.

At the same time, it also allows two callback functions to be specified for each state, taking the green state as an example:

  • onleavegreen : Triggered when leaving the green state.
  • onentergreen (can be abbreviated as ongreen): Triggered when entering the greeen state.

Assuming that the warn event causes the state to change from green to yellow, the above four types of callback functions occur in the following order: onbeforewarn → onleavegreen → onenteryellow → onafterwarn.

In addition to assigning a callback function individually for each event and state, you can also specify a generic callback function for all events and states.

  • onbeforeevent: Triggered before any event occurs
  • onleavestate: Triggered when leaving any state.
  • onenterstate: Triggered when entering any state.
  • onafterevent: Triggered after any event occurs

If the event callback has asynchronous operations (such as Ajax), then we may wish to wait until the asynchronous operation is finished, and then change the state. Here we need to use the transition method

fsm.onleavegreen = function () {
  light.fadeOut('slow', function () {
    fsm.transition();
  });
  return StateMachine.ASYNC;
};

In the callback shown above, there is an asynchronous operation (light.fadeOut). If you do not want the state to change immediately, let the callback return StateMachine.ASYNC, which means that state temporarily does not change. After the asynchronous operation is completed, then call the transition method to make the state change.

Javascript Finite State Machine also allows you to specify an error handler that automatically triggers when an event that should not occur in the current state occurs.

const fsm = 
      .create({
  // ...
  error: function (eventName, from, to, args, errorCode, errorMessage) {
    return 'event ' + eventName + ': ' + errorMessage;
  },
  // ... 
});

For example, the current state is green, theoretically only warn events can occur at this time. If a stop event occurs in that case, it will trigger the above error handling function.

Javascript Finite State Machine basic usage is the above, a more detailed description can refer to its home page .

Bitnami