• Memory Leaks in node. js: Best Practices for Performance
  • Deepu K Sasidharan
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: laampui
  • Proofreader: Usualminds, Regon-Cao

Avoiding memory leaks in Node.js: Performance Best Practices

Memory leaks are a problem that every developer will eventually encounter. It exists in most programming languages, even those that manage memory automatically. Memory leaks can cause problems such as slow applications, crashes, and high latency.

In this article, you’ll learn what memory leaks are and how to avoid them in Node.js. Although this article focuses on NodeJS, it should also apply to JavaScript and TypeScript. Avoiding memory leaks can help your application use resources more efficiently while also improving performance.

Memory management in JavaScript

To understand memory leaks, we first need to understand how NodeJS manages memory. This means you need to understand how memory is managed by NodeJS’s JavaScript engine. For JavaScript, NodeJS uses the V8 Engine. For a better understanding of how memory is organized and utilized by V8, see Visualizing Memory Management in V8 Engine.

To summarize the reference article mentioned above:

Memory is mainly divided into stacks and heaps.

  • Stack: Holds static data, including method (function) frames, raw values, and Pointers to objects. The space here is managed by the operating system.
  • Heap: V8 holds objects or dynamic data. This is the largest area of memory and this is where garbage collection (GC) takes effect.

V8 manages heap memory through garbage collection. Simply put, it frees objects that are not referenced. For example, any object that is not directly or indirectly referenced by the stack (by another object) is freed for the creation of a new object.

The V8 garbage collector is responsible for collecting unwanted memory and making it available to the V8 process for reuse. The V8 garbage collector differentiates between old and new generations (objects in the heap are grouped by their age and cleaned up at different stages). V8 garbage collection has two phases and three algorithms.

What is a memory leak

Simply put, a memory leak is an isolated chunk of memory on the heap that is no longer being used by the program and is not released back to the operating system by the garbage collector, so it is an unused chunk of memory. This continued increase may cause your application to run out of memory space to continue working, or your operating system may not have enough memory to allocate, causing the system to slow down or crash.

What caused the memory leak

Automatic memory management (such as garbage collection in V8) is designed to avoid memory leaks. Things like circular references are no longer a concern for developers, but memory leaks can still happen, either because of unexpected references in the heap or for a variety of reasons. Some common reasons are listed below.

  • The global variable: Because global variables in JavaScript are handled by the root node (window or global)this) references, so they will not be collected throughout the application life cycle, which means they will remain in memory. The same applies to objects that are referenced by global variables (or subattributes), as referencing a large number of objects through the root node can lead to memory leaks.
  • Multiple references: When the same object is referenced by multiple objects, a memory leak can occur when one of the references is suspended.
  • Closures: One cool feature of JavaScript closures is the ability to store the context associated with them. When a closure holds a reference to a large object in the heap, the closure keeps that large object in memory until the closure no longer uses it. This means you could easily get into a situation where a closure holding a reference is used incorrectly, resulting in a memory leak.
  • Timers & Events: Using setTimeout, setInterval, Observers, and event listeners, they can cause memory leaks if they do not properly handle large object references stored in their callbacks.

Best practices for avoiding memory leaks

Now that we understand what causes memory leaks, let’s take a look at how to avoid them and learn best practices for ensuring efficient memory use.

Reduce the use of global variables

Since global variables are never garbage collected, it is best not to overuse them. Here are some ways:

Avoid unexpected global variables

When you assign a value to an undeclared variable, JavaScript automatically promotes it to global by default. This can be a low-level error that causes a memory leak. Another case is to assign this (something still sacred in JavaScript).

// Foo is promoted to a global variable
function hello() {
    foo = "Message";
}

// Foo also becomes a global variable, because in non-strict mode, \ 'this\' in a global function points to the global context
function hello() {
    this.foo = "Message";
}
Copy the code

To avoid these surprises, always use ‘use strict’ at the top of the JS file; Write JavaScript in strict mode. In strict mode, the above example will report an error. When you use ES Modules or interpreters such as TypeScript or Babel, you don’t need to declare strict modes because these tools are already turned on automatically. In recent NodeJS versions, you can turn on global strict mode with the –use_strict parameter when using the node command.

"use strict";

// Foo is not promoted to the global environment
function hello() {
    foo = "Message"; // A runtime error is thrown
}

// Foo does not become a global variable because, strictly speaking, the \ 'this\' of a global function points to itself
function hello() {
    this.foo = "Message";
}
Copy the code

When using arrow functions, you also need to be careful not to accidentally create global variables. Unfortunately, strict mode does not help in this case, you can use the no-invalid-this rule from ESLint to avoid this situation. If you don’t use ESLint, make sure you don’t assign this to global arrow functions.

// foo becomes a global variable because the arrow function doesn't have 'this'. It looks up the lexical scope for the nearest' this'
const hello = () = > {
    this.foo = "Message";
}
Copy the code

Finally, remember not to use the bind or call methods to bind this to any function, as this defeats the purpose of using a strict pattern.

Use global scopes sparingly

In general, it’s a good practice to avoid using global scopes and global variables whenever possible.

  1. Whenever possible, do not use global scopes; instead, use local scopes of functions, as they are garbage collected and memory is freed. If you must use a global variable for some reason, set its value when you no longer use itnull.
  2. Use global variables only for constants, cached, and reusable singletons. Do not use global variables because of the hassle of passing arguments. For sharing data between functions and classes, pass them as parameters or object attributes.
  3. Do not store large objects in the global scope. If you must store them, be sure to assign them to NULL when you no longer need them. For cached objects, you can write a utility function to periodically clean them up and keep them from growing indefinitely.

Efficient use of stack memory

Using stack variables whenever possible helps with memory and performance, since accessing the stack is much faster than accessing the heap, and ensures that we don’t accidentally create memory leaks. Of course, using static data alone is unrealistic. In real world applications, we will have to use a lot of objects and dynamic data, but we can learn some tricks to make better use of stacks.

  1. Avoid referring to heap objects from stack variables, and never keep unused variables.
  2. Instead of passing the entire object or array to a function, closure, timer, or event handler, use destructuring to get the required fields from an object or array. This avoids the closure keeping a reference to an object. The fields retrieved are often raw values, and the raw values are stored on the stack.
function outer() {
    const obj = {
        foo: 1.bar: "hello"};const closure = () {
        const{ foo } = obj; myFunc(foo); }}function myFunc(foo) {}
Copy the code

Efficient use of heap memory

It is often unavoidable to use heap memory in practical applications, but we can follow these tips to make more efficient use of heap memory:

  1. Instead of passing references, copy objects whenever possible, passing references only when the object is large and the copying operation is expensive.
  2. Avoid object operations whenever possible, and instead use object extensions orObject.assignTo copy them.
  3. Avoid creating multiple references to the same object. Instead, make a copy of the object.
  4. Use transient survival variables.
  5. Avoid creating objects that are too deeply nested, and if you can’t avoid it, clean them up in the current scope.

Use closures, timers, and event handlers appropriately

As we saw earlier, closures, timers, and event handlers are common places for memory leaks. Let’s start with closures, which are the most common code in JavaScript. Take a look at the following code from the Meteor team, which causes a memory leak because longStr is never reclaimed and continues to consume memory, read this blog for more details.

var theThing = null;
var replaceThing = function () {
    var originalThing = theThing;
    var unused = function () {
        if (originalThing) console.log("hi");
    };
    theThing = {
        longStr: new Array(1000000).join("*"),
        someMethod: function () {
            console.log(someMessage); }}; };setInterval(replaceThing, 1000);
Copy the code

The above code creates multiple closures, and these closures hold object references. In this case, memory leaks can be avoided by setting the value of originalThing to NULL at the end of the replaceThing function. This can also be avoided by creating a copy of the object, or by referring to several of the methods mentioned earlier.

As for timers, always remember to pass a copy of an object and avoid modifying it. Also, when the timer ends, remember to use the clearTimeout and clearInterval methods.

The same goes for event listeners and observers; clean them up when they’re done, and don’t let event listeners run forever, especially if they hold object references to their parent scope.

conclusion

Thanks to the evolution of JS engines and the optimization of the language itself, memory leaks in JavaScript are not as big a problem as they used to be, but they can still happen and cause performance problems and even application or operating system crashes if we are careless. The first step to ensuring that our code does not leak memory is to understand how the V8 engine manages memory. The second step is to understand what is causing the memory leak. Once we understand these two points, we can do our best to avoid situations that lead to memory leaks. When we do face a memory leak or performance problem, we know where to go. As for NodeJS, there are tools that can help. For example, Node-memwatch and Node-Inspector are excellent tools for debugging memory problems.

reference

  • Memory leak patterns in JavaScript
  • Memory Management
  • Cross-Browser Event Handling Using Plain ole JavaScript
  • Four types of leaks in your JavaScript code and how to get rid of them
  • An interesting kind of JS memory leak

If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.