For developers, JavaScript memory management is automatic and invisible. The raw values, objects, functions we create… All this takes up memory.

What happens when we don’t need something anymore? How does the JavaScript engine find it and clean it up?

Reachability

The main memory management concept in JavaScript is reachability.

In short, “reachable” values are those that are accessible or available in some way. They must be stored in memory.

  1. Here is a basic set of inherently reachable values that obviously cannot be released.

For example:

  • Local variables and arguments to the current function.
  • When a nested call is made, the variables and arguments of all functions on the current call chain are called.
  • Global variables.
  • (And some internal ones)

These values are called roots.

  1. A value is considered reachable if any other value can be accessed from the root through a reference or chain of references.

For example, if there is an object in a global variable and that object has a property that references another object, that object is considered reachable. And what it references is also reachable. The following is a detailed example.

There’s something called the garbage collector that runs in the background in the JavaScript engine. It monitors the status of all objects and removes those that are no longer reachable.

Here’s a simple example:

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



The arrow here describes an object reference. The global variable “user” refers to the object {name: “John”} (let’s call it John for brevity). John’s “name” property stores a raw value, so it is written inside the object.

If the value of user is overridden, the reference is lost:

user = null;
Copy the code



Now John is unreachable. Because there is no reference, you cannot access it. The garbage collector considers it garbage data, reclaims it, and frees memory.

Two references

Now let’s imagine that we copy a reference to user to admin:

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



Now if you do the operation you just did:

user = null;
Copy the code

… Then the object can still be accessed using the admin global variable, so the object is still in memory. If we overwrite admin again, the object will be deleted.

Objects that are interrelated

Now for a more complicated example. This is a family:

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 marry function “marries” two objects by referring to each other and returns a new object containing them.

The resulting memory structure:

So far, all objects are reachable.

Now let’s remove two references:

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



It is not enough to delete one of these two references because all objects are still reachable.

However, if we delete both of these, we can see that there are no more references to John:

External references are not important; only incoming references make objects reachable. So, John is now unreachable and will be removed from memory, and all of John’s data will become unreachable.

After recycling:

Inaccessible islands

Several objects refer to each other, but there are no external references to any of their objects, which may also be unreachable and removed from memory.

The source object is the same as above. And then:

family = null;
Copy the code

Internal memory state will become:

This example demonstrates the importance of the concept of accessibility.

Obviously, John and Ann are still attached to each other, and both have incoming references. But that is not enough.

The “family” object is no longer attached to the root and has no external references to it, so it becomes an island and will be removed from memory.

Internal algorithm

The basic algorithm for garbage collection is called “Mark-and-sweep.”

Perform the following garbage collection steps periodically:

  • The garbage collector finds all the roots and “marks” (remembers) them.
  • It then iterates over and “marks” all references from them.
  • It then iterates over the tagged objects and marks their references. All traversed objects are remembered so that the same object is not traversed again in the future.
  • … Do so until all reachable (from the root) references are accessed.
  • Objects that are not marked are deleted.

For example, make our object have the following structure:

We can clearly see an “unreachable island” on the right. Now let’s see how the Mark and clear garbage collector handles it.

The first step marks all the roots:

Then their references are marked:

… If there are references, continue marking:

Objects that are not reachable through this procedure are now considered unreachable and will be deleted.

We can also think of this process as spilling a giant bucket of paint from the root, flowing through all references and marking all reachable objects. Then remove the unmarked.

This is the concept of garbage collection. The JavaScript engine has made a number of optimizations to make garbage collection run faster without affecting normal code execution.

Some optimization suggestions:

  • Generational collection — objects were divided into two groups: “new” and “old.” Many objects appear, do their job and die quickly, and they can be cleaned up quickly. Those that survive for a long time become “old” and are examined less often.
  • Incremental collection — If there are many objects and we try to traverse and mark the entire set of objects at once, this can take some time and introduce significant delays in execution. So the engine tries to break up garbage collection into several parts. Then these parts will be processed one by one. This requires extra markers between them to track changes, but there are many small delays instead of one big one.
  • Idle-time collection — The garbage collector will only attempt to run when the CPU is Idle to reduce possible impact on code execution.

There are other garbage collection algorithm optimizations and styles. Although I want to describe them here, I have to stop there because different engines have different tweaks and tricks. And, more importantly, things change as the engine evolves, so it’s not worth learning these things “up front” when there’s no real need. Unless, of course, it’s a purely mercenary relationship. I’ve provided you with some links below.

conclusion

Main contents to master:

  • Garbage collection is automatic and cannot be enforced or prevented.
  • When an object is reachable, it must exist in memory.
  • Being referenced is not the same as being accessible (from a root) : a set of interconnected objects may not be reachable as a whole.

Modern engines implement advanced algorithms for garbage collection.

The Garbage Collection Handbook: The Art of Automatic Memory Management (R. Jones et al.) covers some of this.

Excerpted from javascript. Info