Home > Article > Web Front-end > Detailed explanation of memory management in JavaScript
Related recommendations: "javascript video tutorial"
Most of the time, we only develop without understanding the knowledge about memory management. Because the JS engine will handle this for us. However, sometimes we encounter problems such as memory leaks. Only by knowing how memory allocation works can we solve these problems.
In this article, we mainly introduce the working principles of memory allocation and garbage collection and how to avoid some common memory leaks problems.
In JS, when we create a variable, function or any object, the JS engine allocates memory for it and releases it when it is no longer needed.
Allocate memory is the process of reserving space in memory, while Release memory releases space to prepare it for other purposes.
Every time we allocate a variable or create a function, the storage of that variable goes through the same stages:
Allocating Memory
Using memory
Release memory
"Object" in the context of memory management includes not only JS objects, but also functions and function scopes.
Now we know that for everything we define in JS, the engine allocates memory and frees it when the memory is no longer needed.
The next question that comes to my mind is: Where will these things be stored?
The JS engine can store data in two places: Memory heap and Stack. Heap and stack are two data structures used by the engine for different purposes.
The stack is a data structure used by JS to store static data. Static data is data whose size the engine knows at compile time. In JS, includes primitive values pointing to objects and functions (strings
, number
, boolean
, undefined
, and null
) and reference types.
Since the engine knows that the size will not change, it will allocate a fixed amount of memory for each value.
The process of allocating memory immediately before execution is called static memory allocation. The limits of these values and the entire stack are browser dependent.
Heap is another space for storing data, where JS stores objects and functions.
Unlike the stack, the JS engine does not allocate a fixed amount of memory for these objects, but allocates space as needed. This way of allocating memory is also called Dynamic Memory Allocation.
The characteristics of these two stores will be compared below:
Stack | Heap |
---|---|
Storage basic types and references The size is known at compile time Allocate a fixed amount of memory |
Objects and functions The size is not known until runtime No restrictions |
Let’s take a few examples to enhance the image.
const person = { name: 'John', age: 24, };
JS allocates memory for this object in the heap. The actual values are still the original values, that's why they are stored on the stack.
const hobbies = ['hiking', 'reading'];
Arrays are also objects, that's why they are stored in the heap.
let name = 'John'; // 为字符串分配内存 const age = 24; // 为字分配内存 name = 'John Doe'; // 为新字符串分配内存 const firstName = name.slice(0,4); // 为新字符串分配内存
The initial value is immutable, so JS does not change the original value, but creates a new value.
All variables first point to the stack. If it is a non-primitive value, the stack
contains a reference to the object in the heap
.
Heap memory is not sorted in a specific way, so we need to keep a reference to it on the stack. We can think of references
as addresses and the objects in the heap as the houses to which those addresses belong.
Remember that JS stores objects and functions on the heap. Primitive types and references are stored on the stack.
In this photo, we can observe how different values are stored. Notice how person
and newPerson
both point to the same object.
const person = { name: 'John', age: 24, };
This will create a new object in the heap and create a reference to the object on the stack.
Now, we know how JS allocates memory for various objects, but in the memory life cycle, there is one last step: Release memory.
Just like memory allocation, the JavaScript engine also handles this step for us. More specifically, the Garbage Collector is responsible for this job.
Once the JS engine recognizes that a variable or function is no longer needed, it will release the memory it occupied.
The main problem with this is that whether some memory is still needed is an undecidable question, which means that it is impossible to have an algorithm that can immediately collect all the memory that is no longer needed the moment it is no longer needed.
Some algorithms can solve this problem well. I'll discuss the most common methods in this section: Reference counting
and Mark clearing
algorithms.
When a variable is declared and a reference type value is assigned to the variable, the number of references to this value is 1
. If the same value is assigned to another variable, the number of references to the value is increased by 1
. On the contrary, if the variable containing a reference to this value obtains another value, the number of references to this value is reduced by 1
.
When the number of references to this value becomes 0
, it means that there is no way to access this value anymore, so the memory space it occupies can be reclaimed. This way, the next time the garbage collector runs, it will free the memory occupied by values with zero references.
Let’s look at the example below.
Please note that in the last frame, only hobbies
are left in the heap, because the last reference is the object.
Reference Counting
The problem with the algorithm is that it does not account for circular references. This happens when one or more objects refer to each other but they can no longer be accessed through code.
let son = { name: 'John', }; let dad = { name: 'Johnson', } son.dad = dad; dad.son = son; son = null; dad = null;
Since the parent objects refer to each other, the algorithm does not release the allocated memory and we can no longer access both objects.
Setting them to null
will not cause the reference counting algorithm to recognize that they are no longer used since they all have incoming references.
The mark and clear algorithm has solutions for cyclic dependencies. It detects whether they are accessible from the root
object instead of simply calculating the reference to the given object.
The browser's root
is the window
object, while the root
in NodeJS is global
.
This algorithm marks unreachable objects as garbage and then scans (collects) them. The root object will never be collected.
This way, circular dependencies are no longer a problem. In the previous example, neither the dad
object nor the son
object is accessible from the root. Therefore, they will all be marked as garbage and collected.
This algorithm has been implemented in all modern browsers since 2012. Only performance and implementation have been improved, but the core idea of the algorithm remains the same.
Automatic garbage collection allows us to focus on building applications instead of wasting time on memory management. However, there are trade-offs.
Because the algorithm does not know exactly when memory is no longer needed, a JS application may use more memory than it actually needs.
Even if an object is marked as garbage, it is up to the garbage collector to decide when and if the allocated memory will be collected.
如果你希望应用程序尽可能提高内存效率,那么最好使用低级语言。 但是请记住,这需要权衡取舍。
收集垃圾的算法通常会定期运行以清理未使用的对象。
问题是我们开发人员不知道何时会回收。 收集大量垃圾或频繁收集垃圾可能会影响性能。然而,用户或开发人员通常不会注意到这种影响。
在全局变量中存储数据,最常见内存问题可能是内存泄漏。
在浏览器的 JS 中,如果省略var
,const
或let
,则变量会被加到window
对象中。
users = getUsers();
在严格模式下可以避免这种情况。
除了意外地将变量添加到根目录之外,在许多情况下,我们需要这样来使用全局变量,但是一旦不需要时,要记得手动的把它释放了。
释放它很简单,把 null
给它就行了。
window.users = null;
忘记计时器和回调可以使我们的应用程序的内存使用量增加。 特别是在单页应用程序(SPA)中,在动态添加事件侦听器和回调时必须小心。
const object = {}; const intervalId = setInterval(function() { // 这里使用的所有东西都无法收集直到清除`setInterval` doSomething(object); }, 2000);
上面的代码每2秒运行一次该函数。 如果我们的项目中有这样的代码,很有可能不需要一直运行它。
只要setInterval
没有被取消,则其中的引用对象就不会被垃圾回收。
确保在不再需要时清除它。
clearInterval(intervalId);
假设我们向按钮添加了onclick
侦听器,之后该按钮将被删除。旧的浏览器无法收集侦听器,但是如今,这不再是问题。
不过,当我们不再需要事件侦听器时,删除它们仍然是一个好的做法。
const element = document.getElementById('button'); const onClick = () => alert('hi'); element.addEventListener('click', onClick); element.removeEventListener('click', onClick); element.parentNode.removeChild(element);
内存泄漏与前面的内存泄漏类似:它发生在用 JS 存储DOM
元素时。
const elements = []; const element = document.getElementById('button'); elements.push(element); function removeAllElements() { elements.forEach((item) => { document.body.removeChild(document.getElementById(item.id)) }); }
删除这些元素时,我们还需要确保也从数组中删除该元素。否则,将无法收集这些DOM元素。
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); }); }
由于每个DOM元素也保留对其父节点的引用,因此可以防止垃圾收集器收集元素的父元素和子元素。
在本文中,我们总结了 JS 中内存管理的核心概念。写这篇文章可以帮助我们理清一些我们不完全理解的概念。
希望这篇对你有所帮助,我们下期再见,记得三连哦!
原文地址:https://felixgerschau.com/javascript-memory-management/
作者:Ahmad shaded
译文地址:https://segmentfault.com/a/1190000037651993
更多编程相关知识,请访问:编程入门!!
The above is the detailed content of Detailed explanation of memory management in JavaScript. For more information, please follow other related articles on the PHP Chinese website!