A few weeks ago, we opened a series of articles aimed at delving into JavaScript and exploring its workings. We think we can write better code and application by understanding how JavaScript is built and how it is run.
The first article focuses on an overview of the engine, runtime, and call stacks. The second article carefully analyzes the internals of Google's V8 JavaScript engine and offers some advice on how to write better JavaScript code.
This is the third article, and we'll talk about a topic called memory management that developers ignore because of the growing sophistication and complexity of everyday programming languages. We will also provide some advice on how to handle JavaScript memory leaks in Sessionstack, as we need to ensure that sessionstack does not cause memory leaks and does not increase the memory consumption of our integrated WEB applications. Overview
Like the C language, it has the underlying original memory management methods, such as malloc () and free (). These original methods were used by developers to allocate memory and free memory from the operating system.
However, JavaScript allocates memory when something (objects,strings,etc.) is created and "automatically" releases them when they are no longer in use, a process known as garbage collection.
The seemingly "automatic" nature of releasing resources is the cause of the obsession. It gives JavaScript (and other advanced language) developers a false impression-they can choose not to care about memory management. This is a big mistake.
Even with a high-level language, developers should have a sense of memory management (at least a basic understanding). Sometimes, developers must understand that automatic memory management encounters problems (such as bugs or performance issues in garbage collection) so that they can be handled correctly. (or find the right solution, with the smallest price to solve.) ) the life cycle of memory
No matter what language you use, the life cycle of memory is basically the same:
An overview of what happens every step of the lifecycle: the Allocate memory--operating system allocates memory, allowing your program to use it. In the underlying language (for example, C), this is a clear operation that a developer handles for itself. However, in the advanced language, it has been handled for you. Use memory--Now you have the memory allocated before. When you use variables in your code, the read and write operations are occurring. Release memory--Now it's time to free up the memory you no longer need so that they can be used again. Like an operation that allocates memory, this action is explicitly performed in the underlying language.
To quickly understand the concept of the call stack and memory heap, you can read the first article of our topic. What when memory
Before we discuss memory directly in JavaScript, let's briefly discuss what general memory is and how it works.
At the hardware level, the computer memory is composed of a large number of triggering circuits. Each trigger circuit contains a number of transistors and can store a bit. A single trigger can be addressed by a unique identifier, so we can read and override them. So conceptually, we can look at the entire computer memory as a large array of bits that we can read and write.
Because as humans, we are not very good at sitting on the thinking and calculation, so we will be a bit of a larger group, they can be used together to represent numbers. 8 bits are called 1 bytes (byte). In addition to bytes, there are also words (sometimes 16 bits, sometimes 32 bits).
Many things are stored in memory: All variables and other data used by all programs. The code of the program, including the operating system.
The compiler and the operating system work together to help you manage memory, but we recommend that you look at the contents under the hood.
When compiling code, the compiler can examine the original data type and calculate in advance how much memory is required. The required quantity is then assigned to the program in the call stack. The space allocated by these variables is called the stack space, because as the function is invoked, their memory is added to the top of the existing memory. When they terminate, they are removed in the LIFO (last-in,first-out) Order. For example, consider the following statement:
int n; 4 bytes
int x[4];//array of 4 elements, each 4 bytes
double m;//8 bytes
The compiler will immediately see that this code requires:
4 + 4x4 + 8 = bytes.
This is now the working principle of integer and double precision. About 20 years ago, integers were usually 2 bytes, with a double of 4 bytes. Your code should never depend on the size of the base data type at this time.
The compiler inserts code that interacts with the operating system to request the number of bytes required for the variable to be stored in the stack.
In the example above, the compiler knows the exact memory address of each variable. In fact, every time we write a variable n, it is translated as "memory address 4127963" inside the system.
Note that if we try to access x[4 here, we may be able to access the data associated with M. This is because the element we are accessing does not exist in the array-these 4 bytes are farther away than the last element in the array x[3], and may read (or override) the bit of M. This will certainly have an incomprehensible adverse effect on the program.
When a function calls another function, each function obtains its own stack block when called. It holds all the local variables and a program counter that remembers where the function is in execution. When a function completes, its memory block can be used again for other purposes. Dynamic Assignment
Unfortunately, when we compile a variable that does not know how much memory, things become less easy. Suppose we have to do the following things:
int n = readinput (); Reads input from the user ...
Create an array with "n" Elements
In this way, at compile time, the compiler does not know how much memory the array needs because it relies on input provided by the user.
Therefore, it cannot allocate space for variables on the stack. Instead, our program needs to explicitly require the operating system to get the appropriate amount of space at runtime. This memory is allocated from the heap space. The difference between static and dynamic memory allocations is shown in the following table:
Static Allocation |
Dynamic Allocation |
Compile-time Memory size determination |
Compile-time memory size is indeterminate |
Compile phase execution |
Run-time execution |
assigning to Stacks |
assigning to Heaps |
FILO |
Not in a specific order. |
In order to fully understand how dynamic memory allocation works, we need to spend more time on pointers, which may deviate from the topic of this article. If you are interested, please let us know in the comments, we can introduce the pointers in future articles in detail. Allocations in JavaScript
Now we're going to explain how the first step (allocating memory) in JavaScript works.
JavaScript frees developers from the responsibility to handle memory allocation--javascript himself when declaring values.
var n = 374; Allocates memory for a number
var s = ' sessionstack ';//allocates memory for a string
var o = {
a:1,
B:null
}; Allocates memory for the object and its contained values
var a = [1, NULL, ' str ']; (like object) allocates memory for the "
//array and its contained values
function f (a) {return
A + 3;
}//allocates a function (which is a callable object)
/function expressions also allocate an object
Someelem Ent.addeventlistener (' click ', Function () {
someElement.style.backgroundColor = ' blue ';
}, False);
Some function calls can also cause an object to be allocated:
var d = new Date (); Allocates a Date object
var e = document.createelement (' div ');//allocates a DOM element
method to assign a new value or object:
var s1 = ' Sessionstack ';
var s2 = s1.substr (0, 3); S2 is a new string
//Since strings are immutable,
//JavaScript could decide to not allocate memory,
//But Just store the [0, 3] range.
var a1 = [' str1 ', ' str2 '];
var a2 = [' Str3 ', ' STR4 '];
var a3 = A1.concat (A2);
New array with 4 elements being
//the concatenation of A1 and A2 elements
using memory in JavaScript
Basically, the use of memory in JavaScript means that there is a read and write inside.
This operation may be read or written to a variable value, read or write to an object property, or even pass arguments to a function. release the memory when it is no longer needed
Most memory management problems occur at this stage.
The hardest task here is to determine when the memory will no longer be needed. It typically requires developers to determine where such memory is no longer needed in the program and to release it.
The high-level language has a garbage collector, which is responsible for tracking memory allocations and usage, finding memory that is no longer in use, and automatically releasing it.
Unfortunately, this process can only get a myopic value, because the memory is not required to be determined (can not be solved by the algorithm).
Most garbage collector works by determining whether memory can be accessed again, for example: all variables pointing to it are outside the scope. However, this can only get an approximate value. Because at any location, the memory location may still have a variable that points to its scope, but it may never be accessed again. Garbage Collection
Since the fact that memory is "no longer needed" is not an option, the usual solution for garbage collection has limitations. This section describes the concepts necessary to understand the primary garbage collection algorithms and their limitations. Memory Reference
The main concept that the garbage collection algorithm relies on is reference.
In the context of memory management, if the former has access to the latter (which can be implicit or explicit), an object becomes a reference to another object. For example, a JavaScript object has a reference to its prototype (an implicit reference) and its property value (explicit reference).
In the context, the definition of "object" extends to something broader than a regular JavaScript object, and also includes the scope of the function (or the global lexical scope).
Lexical scopes define how to resolve variable names in nested functions: Even if the parent function is returned, the intrinsic function can contain the scope of the parent function. reference count--garbage collection
This is the simplest garbage collection algorithm. If an object points to its reference number of 0, it should be "garbage collected".
Take a look at the following code:
var O1 = {o2: {x:1}};
2 objects are created.
' O2 ' is referenced by ' O1 ' object as one of its properties. None can be garbage-collected var O3 = O1;
The ' O3 ' variable is the second thing which//has a reference to the object pointed by ' O1 '. O1 = 1; Now, the object is originally in ' O1 ' has a//single reference, embodied by the ' O3 ' Variab Le var O4 = O3.o2;
Reference to ' O2 ' of the object.
This object is has now 2 references:one AS//A. The other as the ' O4 ' variable o3 = ' 374 ';
The object that is originally in ' O1 ' has now zero//references to it.
It can be garbage-collected.
However, what was it's ' O2 ' property is still//referenced by the ' O4 ' variable, so it cannot to be
Freed. O4 = null; What was the' O2 ' of the object originally in//' O1 ' has zero references to it. It can be garbage collected.
problems caused by cyclic dependency
There is a limit to the occurrence of cyclic dependencies. In the following example, two objects are created and referenced to each other, creating a loop. After the function call, they leave the scope, so they are actually useless and can be freed. However, the reference counting algorithm considers that since each of the two objects is at least one reference, it cannot be garbage collected.
function f () {
var O1 = {};
var O2 = {};
O1.P = O2; O1 references o2
o2.p = O1;//O2 references. This creates a cycle.
}
f ();
tag Scan algorithm
To determine whether an object is needed, this algorithm determines whether an object can be accessed.
The algorithm consists of the following steps: The garbage collector builds the "Roots" list. Roots is typically a global variable in code that retains a reference. In JavaScript, the "window" object can be used as an example of the root global variable. All roots are checked and marked as active (that is, not garbage). All the children are also recursively checked. Everything that can be reached from root is not considered rubbish. All memory marked as active can be considered garbage. Collector limits allow these memory to be freed and returned to the operating system.
This algorithm is superior to the previous one because "an object 0 reference" will make this object not accessible. The converse is not necessarily right because there is a circular reference.
As of 2012, all modern browsers are equipped with the mark-and-sweep mechanism of the garbage collector. In the past few years, all the improvements in JavaScript garbage collection (algebraic, incremental, parallel, parallel, garbage collections) have improved the implementation of the algorithm (Mark-and-sweep), but the garbage collection algorithm itself has not been improved, with the goal of determining whether an object can be reached.
In this article, you can read more details about garbage collection and, of course, optimize it. Circular references are no longer an issue
In the first example above, after a function call, two objects are no longer referenced by something that is accessible from the global object. As a result, the garbage collector will find that they are unreachable.
Although two objects still have references, they cannot be reached from root. the direct behavior of the anti-garbage collector
Although garbage collectors are convenient, they also have their own series of decisions. One of them is a right and wrong theory. In other words, GCs are unpredictable. You can't really know when a recycle is going to be performed. This means that in some cases, the program uses more memory than is actually needed. In other cases, if the program is particularly sensitive, some temporary pauses are particularly noticeable. Although the uncertainty means that the time of the recycle execution cannot be determined, most GCs implementations are shared patterns-a collection traversal is performed during allocating memory. If no execution is assigned, most GCs remain idle. Consider the situation: a fairly large set of allocations is performed. Most of the elements (or all) are marked as unreachable (assuming that we will point to a cached reference null that is no longer needed). No more allocations are made.
In this case, most GC will no longer run any further collections. In other words, even if an unreachable reference variable can be collected, the collector is not declared. These are not strict memory leaks, but they can still lead to higher memory usage than normal memory. What is a memory leak
Essentially, a memory leak can be defined as memory that the application no longer needs, but for some reason the memory is not returned to the operating system or the available memory pool.
Programming languages support a variety of ways to manage memory. However, whether or not a block of memory is being used is actually an issue of uncertainty. In other words, only a developer can understand whether a piece of memory can be released to the operating system or should not be released.
Some programming languages provide features that help developers deal with these things. Other languages expect developers to be able to explicitly control memory entirely by themselves. Wikipedia has good articles on manual and automatic memory management. four kinds of common JAVASCR