JavaScript memory management fundamentals: how it works and what issues can arise





Most developers rarely think about how JavaScript memory management is implemented. The engine usually does everything for the programmer, so it makes no sense for the latter to think about the principles of the memory management mechanism.



But sooner or later, developers still have to deal with memory problems, such as leaks. Well, it will be possible to deal with them only when there is an understanding of the memory allocation mechanism. This article is devoted to explanations. It also provides tips on the most common types of memory leaks in JavaScript.



Memory life cycle



When creating functions, variables, etc. in JavaScript, the engine allocates a certain amount of memory. Then he frees it - after the memory is no longer required.



Actually, memory allocation can be called the process of reserving a certain amount of memory. Well, its release is the return of the reserve to the system. You can reuse it as many times as you like.



When a variable is declared or a function is created, the memory goes through the next loop.







Here in blocks:



  • Allocate is the memory allocation that the engine does. It allocates the memory that is required for the created object.
  • Use - memory usage. The developer is responsible for this moment, writing in the code to read and write to memory.
  • Release - freeing memory. This is where JavaScript comes into its own again. After the reserve is released, the memory can be used for other purposes as well.


“Objects” in the context of memory management means not only JS objects, but also functions and scopes.



Memory stack and heap



In general, everything seems to be clear - JavaScript allocates memory for everything that the developer specifies in the code, and then, when all operations are completed, the memory is freed. But where is the data stored?



There are two options - on the memory stack and on the heap. What's the first, what's the second - the name of the data structures that are used by the engine for different purposes.



Stack is static memory allocation







The definition of the stack is known to many. It is a data structure that is used to store static data, its size is always known at compile time. JS has included primitive values ​​like string, number, boolean, undefined and null, as well as function and object references.



The engine "understands" that the size of the data does not change, so it allocates a fixed amount of memory for each of the values. The process of allocating memory before execution is called static memory allocation.



Since the browser allocates memory in advance for each data type, there is a limit on the size of the data that can be put there. Since the browser allocates memory in advance for each data type, there is a limit on the size of the data that can be put there.



The heap - dynamic memory allocation



As far as the heap is concerned, it is as familiar to many as the stack. It is used to store objects and functions.



But unlike the stack, the engine cannot "know" how much memory is needed for a particular object, so memory is allocated as needed. And this way of allocating memory is called "dynamic" (dynamic memory allocation).



Some examples



The comments to the code indicate the nuances of memory allocation.



const person = {
  name: 'John',
  age: 24,
};
      
      





// JavaScript allocates memory for this object on the heap.

// The values ​​themselves are primitive, so they are stored on the stack.



const hobbies = ['hiking', 'reading'];
      
      





// Arrays are objects too, so they go to the heap.



let name = 'John'; // allocate memory for the string

const age = 24; // allocate memory for the number

name = 'John Doe'; // allocate memory for a new line

const firstName = name.slice (0,4); // allocate memory for a newline



// Primitive values ​​are inherently immutable: instead of changing the initial value,

// JavaScript creates another one.



JavaScript links



As for the stack, all the variables point to it. If the value is not primitive, the stack contains a reference to the heap object.



There is no particular order in it, which means that a reference to the desired memory area is stored on the stack. In such a situation, the object in the heap looks like a building, but the link is its address.



JS stores objects and functions on the heap. But primitive values ​​and references are on the stack.







This image shows the storage organization of different values. Note that person and newPerson point to the same object here.



Example



const person = {
  name: 'John',
  age: 24,
};
      
      





// A new object is created on the heap, and a reference to it on the stack.



In general, links are extremely important in JavaScript.



Garbage collection



Now is the time to return to the life cycle of memory, namely, its release.



The JavaScript engine is responsible not only for memory allocation, but also for deallocation. In this case, the garbage collector returns memory to the system.



And as soon as the engine "sees" that the variable or function is no longer needed, the memory is freed.



But there is a key problem here. The fact is that it is impossible to decide whether a certain area of ​​memory is needed or not. There are no algorithms so precise that free memory in real time.



True, there are simply well-working algorithms that allow you to do this. They are not perfect, but still much better than many others. Below - a story about garbage collection, which is based on reference counting, as well as about the "flagging algorithm".



What about links?



This is a very simple algorithm. It removes those objects to which no other reference points. Here's an example that explains it pretty well.



If you've watched the video, you've probably noticed that hobbies are the only object in the heap that's still referenced on the stack.



Cycles



The disadvantage of the algorithm is that it is not able to take into account circular references. They occur when one or more objects refer to each other, out of reach from the point of view of the code.



let son = {
  name: 'John',
};
let dad = {
  name: 'Johnson',
}
 
son.dad = dad;
dad.son = son;
son = null;
dad = null;
      
      







Here son and dad refer to each other. There is no access to objects for a long time, but the algorithm does not release the memory allocated for them.



Precisely because the algorithm counts references, assigning null to objects does nothing, since every object still has a reference.



Algorithm for annotations



This is where another algorithm comes to the rescue, which is called the mark and sweep method. This algorithm does not count references, but determines whether different objects can be accessed through the root object. In the browser, this is window, and in Node.js, it is global.







If the object is not available, the algorithm marks it and then removes it. In this case, the root objects are never destroyed. The problem of cyclic references is not relevant here - the algorithm allows us to understand that neither dad nor son are already inaccessible, so they can be "swept away" and memory returned to the system.



Since 2012, absolutely all browsers have been equipped with garbage collectors that work exactly according to the mark and sweep method.



Not without its drawbacks here.





One would think that everything is fine, and now you can forget about memory management, leaving everything to the algorithm. But it is not so.



Large Memory Usage



Because algorithms do not know when memory is no longer needed, JavaScript applications can use more memory than they need to. And only the collector can decide whether or not to free the allocated memory.



JavaScript is better at managing memory in low-level languages. But here, too, there are drawbacks that need to be borne in mind. In particular, JS does not provide memory management tools, unlike low-level languages ​​in which the programmer "manually" deals with memory allocation and release.



Performance



The memory is not cleared every new moment in time. The release is performed at regular intervals. But developers cannot know exactly when these processes are started.



Therefore, in some cases, garbage collection can have a negative impact on performance, because the algorithm needs certain resources to work. True, the situation rarely becomes completely unmanageable. Most often, the consequences of this are microscopic.



Memory leaks



Memory leaks are one of the most frustrating things in development. But if you know all the most common types of leaks, then you can get around the problem without much difficulty.



Global Variables



Memory leaks most often occur due to the storage of data in global variables.



In the browser, if you make a mistake and use var instead of const or let, the engine will attach the variable to the window object. Likewise, it will perform an operation on functions defined by the word function.



user = getUser();
var secondUser = getUser();
function getUser() {
  return 'user';
}
      
      





// All three variables - user, secondUser, and

// getUser - will be attached to the window object.



This can only be done with functions and variables that are declared in the global scope. You can work around this problem by running your code in strict mode.



Global variables are often intentionally declared; this is not always a mistake. BUT, in any case, we must not forget about freeing memory after the data is no longer needed. To do this, you need to assign null to the global variable.



window.users = null;



Callbacks and timers



The application uses more memory than it should, even if we forget about timers and callbacks. The main problem is single page applications (SPA), as well as dynamically adding callbacks and event handlers.



Forgotten timers



const object = {};
const intervalId = setInterval(function() {
  //     ,   ,
  //     
  doSomething(object);
}, 2000);
      
      





This function runs every two seconds. Its implementation should not be endless. The problem is that objects that have a reference in the interval are not destroyed until the interval is cleared. Therefore, you need to prescribe in a timely manner:

clearInterval (intervalId);



Forgotten callback A



problem can arise if an onClick handler is attached to a button, and the button itself is removed after - for example, it is no longer needed.



Previously, most browsers simply could not free the memory allocated for such an event handler. Now this problem is a thing of the past, but still, leaving the handlers that you no longer need is not worth it.



const element = document.getElementById('button');
const onClick = () => alert('hi');
element.addEventListener('click', onClick);
element.removeEventListener('click', onClick);
element.parentNode.removeChild(element);
      
      







Forgotten DOM Elements in Variables



This is similar to the previous case. The error occurs when DOM elements are stored in a variable.



const elements = [];
const element = document.getElementById('button');
elements.push(element);
function removeAllElements() {
  elements.forEach((item) => {
document.body.removeChild(document.getElementById(item.id))
  });
}
      
      





When removing any of the elements above, you should also take care of removing it from the array. Otherwise, the garbage collector will not automatically remove it.



const elements = [];
const element = document.getElementById('button');
elements.push(element);
function removeAllElements() {
  elements.forEach((item, index) => {
document.body.removeChild(document.getElementById(item.id));
   elements.splice(index, 1);
 });
}
      
      





By removing an element from the array, you are updating its content with the list of elements on the page.



Since each house element has a reference to its parent, this referentially prevents the garbage collector from freeing the memory occupied by the parent, which leads to leaks.



In the dry residue



The article describes the general mechanics of memory allocation, as well as the author showed what problems can arise and how to deal with them. All of this is important for any Java Script developer.



, Frontend- Skillbox:



, - ( SPA — Single Page Applications).



, “ ” — ( , ), , , .



— . .



, ( , , ). , , “” — garbage collector.



- (js , garbage collector’a). , .



All Articles