Values of primitive types in JavaScript are stored in stack memory and are automatically allocated and freed by the system. The value of the reference type is stored in heap memory and its lifetime is determined by the JavaScript engine’s garbage collection mechanism.

By far the most popular JavaScript engine is Chrome’s V8 engine. The rest of the discussion is based on V8.

Analysis of recovery mechanism

Garbage collection mechanisms can be broadly divided into reference counting and tag cleaning. Reference counting due to the existence of relatively obvious problems (mainly existing in the early IE browser), today’s mainstream browsers use mark clearing, to manage the memory of reference values.

Reference Counting (Reference Counting)

The basic idea is to keep track of how many times each value is referenced. When a value has zero references, it is safe to reclaim its memory because no variable references it. The garbage collector will free the memory of the value referenced 0 on the next garbage collection.

The problem of citation counting

There is a serious problem with reference counting: circular references. That is, object A has A property pointing to B, and object B has A property pointing to A. Both A and B will have A reference count of 2 and will never be collected by the garbage collector. Over time, a large amount of memory cannot be reclaimed, resulting in memory leaks.

const A = new Object(a);const B = new Object(a); A.ref2B = B; B.ref2A = A;Copy the code

Mark and sweep

With regard to tag clearing, reachability has to be mentioned. Accessibility is an important determinant of whether a value will be garbage collected.

accessibility

Reachable values are those that are accessible or available in some way that will not be cleared or released by the garbage collection mechanism. The following is a set of inherently reachable values that occupy space that cannot be freed.

  • Local variables and arguments to the current function
  • Nested calls call the variables and arguments of all functions in the chain
  • The global variable
  • (And some internal ones)

These values are called roots. A value is considered reachable if it can be accessed from the root through a reference or chain of references.

For example, a

// User has a reference to this object
let user = {
  name: "John"
};
Copy the code

User is the root of the global variable. {name: ‘John’} can be referenced by user, so {name: ‘John’} is reachable and not collected by garbage collection.

If user = null; The user variable is overridden, the reference to the object is cut off, the object becomes unreachable, and the garbage collection mechanism frees the associated memory at the next garbage collection.

For example, two

function marry(man, woman) {
  woman.husband = man;
  man.wife = woman;
  return {
    father: man,
    mother: woman
  }
}

let family = marry({
  name: 'John'
}, {
  name: 'Ann'
});
Copy the code

The famliy variable is a global variable and is root. {father: man, mother: woman} objects can be accessed by reference, father and mother can be accessed by reference chain, and all objects are reachable. Next, remove some references:

delete family.father;
delete family.mother.husband;
Copy the code

After removing the reference, the father object becomes an island. The root cannot access the father object through any reference, so the father object becomes unreachable and the associated memory is reclaimed.

Mark the cleanup process

The garbage collector periodically performs the following steps for garbage collection.

  • Garbage collector finds allThe rootAnd mark them
  • Traverse and mark the reference to the root
  • The references to the references are then marked until all reachable objects are marked
  • Any remaining objects that are not marked are deleted

Dive into V8 garbage collection

In fact, the actual garbage collection strategy is more complicated due to performance requirements that require more efficient algorithms for different scenarios to achieve better results. V8’s garbage collection strategy is based on generational garbage collection.

V8 memory generation

In V8, there are two main memory generations: the new generation and the old generation. Objects in the new generation are objects with a short lifetime, and objects in the old generation are objects with a long lifetime or resident memory.

The Cenozoic type is recycled mainly through the Scavenge algorithm. The Cheney algorithm is used to implement Scavenge.

  • Scavenge algorithm

Cheney is a copy-style garbage collection algorithm that splits the new generation of memory into two parts, each called semispace. Only one of these two Semispace Spaces is in use and the other is idle at any one time. We call the in-use From space and the unused To space. When an object is allocated, it is allocated in the From space first. When garbage collection starts, objects that live in the From space are copied To the To space, and space occupied by non-living objects is freed. After replication is complete, the From and To roles are switched.

The disadvantages of this algorithm are obvious. Due to the space division and replication mechanism, only half of the Cenozoic space can be used. However, since this algorithm only replicates living objects, and the number of living objects in Cenozoic scenes is relatively small, it has excellent performance in time efficiency.

When an object survives multiple copies, it will be considered as an object with a long life cycle, and will be promoted to the old generation space and managed by a new algorithm.

Promotion is based on the application of the Scavenge avenge and the ability To exceed the 25% limit. The reason for setting the 25% limit value is that after the reclamation is completed, the To space will become the From space, and the subsequent memory allocation will be carried out in this space. If the ratio is too high, the subsequent memory allocation efficiency will be affected.

  • Mark-Sweep

The Scavenge avenge is insane because there are so many exploits, and the insane waste half the space. So V8 used a combination of Mark-sweep and Mark-compact for garbage collection in its old days. Mark-sweep has two phases, marking and cleaning, and does not split the space in half, as compared to the Scavenge algorithm, so there is no problem wasting half the space. Mark-sweep iterates through all objects in the old generation in the marking phase, and marks those that are still alive. In the subsequent cleanup phase, only unmarked objects are cleared. As you can see, the Scavenge algorithm only copies the exploiture and mark-sweep only cleans the deactivated. However, the proportion of viable objects in the Cenozoic era is relatively small, and the proportion of inactivated objects in the old generation is relatively small, so it can have higher efficiency.

  • Mark-Compact

The biggest problem of Mark-sweep is that after Mark cleaning, it will cause memory discontinuity. If an object with large memory needs to be allocated later, although the overall free space is sufficient, the memory allocation cannot be completed because the space is not continuous, and garbage collection will be triggered in advance.

To solve mark-Sweep’s memory fragmentation problems, Mark-Compact was born. This algorithm evolved from Mark-sweep. The difference is that after an object is inactivated and marked dead, the surviving object is moved to one end during the process of cleaning. After the move is completed, the memory outside the boundary is cleaned directly. Fixed memory fragmentation problems with Mark-Sweep.

In V8, mark-Sweep was used primarily, and mark-Compact was used when space was scarce to distribute objects promoted from the new generation.

V8 optimization for garbage collection

Because JavaScript is single-threaded, all three algorithms need to suspend the application logic until the garbage collection is complete, which is called total pause. In V8’s generational garbage collection, if only the new generation is collected, even a complete halt doesn’t matter because the default configuration of the new generation is smaller and fewer of them are alive. However, the old generation is usually configured with a large number of surviving objects, so a full heap garbage collection, marking, cleaning and sorting will cause a large pause, affecting the experience.

In order to reduce the pause time for full heap garbage collection, V8 has made some optimizations to the garbage collection process.

  • The incremental

Incremental marking is changed to the marking phase, the marking is sliced, and the marking and application logic are executed alternately. Lazy sweeping is introduced in the clearing stage and incremental sweeping is introduced in the clearing stage.

  • parallel

The concept of parallelism is introduced, and some operations are assigned to auxiliary threads to speed up the execution of operations.

Cooperate with garbage collection

JavaScript engines use complex garbage collection mechanisms in order to make more efficient use of memory space. Developers can combine the following points to better coordinate garbage collection with JavaScript engines.

  1. Avoid accidentally declaring global variables
function foo () {
    unexpectedVar = new Array(100);
}
foo();
Copy the code

UnexpectedVar: foo unexpectedVar declares the global variable unexpectedVar. In browser unexpectedVar becomes an attribute of window, while the window object is unexpectedVar, even when foo is unexpectedunexpected unexpectedVar UnexpectedVar is still referenced by window and cannot be collected, resulting in memory leak.

  1. Use const/let to improve performance

Both const/let variables are declared in block-level scope, which may allow the garbage collector to step in and reclaim the memory space of the variable earlier than var (function scope).

  1. Actively release global variables

Variables in the global environment are resident in memory (old generation space) and cannot be collected by the garbage collector. They need to be released actively. The associated memory space can be freed by delete or by assigning the value to undefined/null.

conclusion

This article starts with an analysis of two garbage collection mechanisms: reference counting and tag cleaning, and then goes into V8’s memory generation mechanism, which adopts different garbage collection algorithms according to the characteristics of new generation space and old generation space. Scavenge, Mark-sweep, and Mark-compact, which is a derivative of mark-sweep. Finally, we discussed how developers can better cooperate with JavaScript engines to implement garbage collection.

reference

  • The garbage collection
  • JavaScript garbage collection mechanism
  • JS garbage collection, this can be read
  • The illustration Google V8
  • Node.js is easy to understand