This article has participated in the third “topic writing” track of the Denver Creators Training Camp. For details, check out: Digg Project | Creators Training Camp third is ongoing, “write” to make a personal impact
preface
To understand the execution mechanism of JavaScript, we can start with the compilation process and memory management. Earlier, I described the JavaScript compilation process in “From Babel to Component On Demand principles.” So today we are going to focus on the process of JavaScript engine memory management.
The memory space of a JavaScript engine is divided into Heap and Stack. A heap is an array with a tree structure, and a stack is an array, but it follows a “first in, last out” rule.
The stack
The stack is a temporary storage space that mainly stores local variables and function calls (anonymous functions are created and called for global expressions).
Local variables of basic datatype (String, Undefined, Null, Boolean, Number, BigInt, Symbol) are created directly on the stack, while object datatype local variables are stored in the stack, and only their reference addresses are stored in the stack, also known as shallow copies. Global variables and closure variables also store only reference addresses. All in all, the data stored in the stack is lightweight.
For functions, the interpreter creates a “Call Stack” to keep track of how the function is called. Each time a function is called, the interpreter adds the function to the call Stack. The interpreter creates a Stack Frame for the added function (the Stack Frame is used to hold the local variables of the function and execute the statement) and executes it immediately. If the executing function also calls other functions, the new function will be added to the call stack and executed. Once this function completes, the corresponding stack frame is destroyed immediately.
There are two ways to view the call stack:
- Call the function console.trace() to print to the console;
- Use browser developer tools for breakpoint debugging.
Example:
The following code is a function that evaluates the Fibonacci sequence and obtains its stack information by calling console.trace() and a breakpoint, respectively.
function fib(n) {
if (n < 3) return 1
console.trace();
return fib(n-1) + fib(n-2)
}
fib(4)
Copy the code
Combined with the debugger:
The stack is created at the end of use and destroyed at the end of use, but it is not infinitely growing. A “stack overflow” error is raised when the allocated call stack space is used up.
The following code snippet is a stack overflow error caused by a recursive function:
(function stackMax() {
stackMax()
})()
Copy the code
Stack overflow error:
The heap
The data stored in heap space is relatively complex and can be roughly divided into the following five areas:
- Code Space
- The Map area (Map Space)
- Large Object Space
- New Space
- Old Space
This paper focuses on the new generation and old generation memory reclamation algorithms.
The new generation
Most objects will initially be allocated in the new generation, which is relatively small, only a few tens of MB of storage space, divided into two Spaces: from space and to space.
The objects declared in the program are first allocated to the FROM space. When garbage collection is performed, the surviving objects in the FROM space are first copied to the TO space for storage, and the unsurviving object space is reclaimed. When copied, the FROM and to Spaces are swapped, and the to space becomes the new FROM space and the original from space becomes the To space, an algorithm called Scavenge.
The new generation of memory reclamation is very frequent and fast, but space utilization is low because half of the memory space is left “idle”.
The old generation
Objects from the Cenozoic generation that are still alive after repeated recycling are transferred to older generations with more space. Because the old generation space is large, if the recycle method still uses Scanvage algorithm to copy objects frequently, the performance overhead will be high.
So the old generation used another mark-sweep method to reclaim non-viable object space.
This method is mainly divided into two stages: marking and clearing. The marking phase iterates through all objects in the heap and marks those that survive; The cleanup phase is to reclaim the space of unmarked objects.
Because tag clearing does not bisect memory, no space is wasted. However, the memory space after marked clearing will produce many discontinuous fragment Spaces. In such discontinuous fragment space, large objects may be unable to be stored due to insufficient space.
In order to solve the problem of memory fragmentation and improve the utilization of memory, it is necessary to use mark-compact algorithm. Compared with the tag cleaning algorithm, the tag cleaning algorithm is improved in the recycling stage. The tag cleaning algorithm does not recycle the unlabeled objects immediately, but moves the surviving objects to one side and then cleans them up. Of course, this operation of moving objects is relatively time consuming, so it is slower to perform than token clearing.
conclusion
JavaScript engine memory is divided into two parts: stack and heap. The stack can hold function call information and local variables. It is characterized by “first in, last out” and “destroy immediately after use”. Data objects stored in the heap are usually large and need to be processed by certain recycling algorithms, including Scanvage algorithm for the new generation, and tag cleaning and tag collation algorithm for the old generation.