Movatterモバイル変換


[0]ホーム

URL:


MDN Web Docs

Memory management

Low-level languages like C, have manual memory management primitives such asmalloc() andfree(). In contrast, JavaScript automatically allocates memory when objects are created and frees it when they are not used anymore (garbage collection). This automaticity is a potential source of confusion: it can give developers the false impression that they don't need to worry about memory management.

Memory life cycle

Regardless of the programming language, the memory life cycle is pretty much always the same:

  1. Allocate the memory you need
  2. Use the allocated memory (read, write)
  3. Release the allocated memory when it is not needed anymore

The second part is explicit in all languages. The first and last parts are explicit in low-level languages but are mostly implicit in high-level languages like JavaScript.

Allocation in JavaScript

Value initialization

In order to not bother the programmer with allocations, JavaScript will automatically allocate memory when values are initially declared.

js
const n = 123; // allocates memory for a numberconst s = "string"; // allocates memory for a stringconst o = {  a: 1,  b: null,}; // allocates memory for an object and contained values// (like object) allocates memory for the array and// contained valuesconst a = [1, null, "str2"];function f(a) {  return a + 2;} // allocates a function (which is a callable object)// function expressions also allocate an objectsomeElement.addEventListener(  "click",  () => {    someElement.style.backgroundColor = "blue";  },  false,);

Allocation via function calls

Some function calls result in object allocation.

js
const d = new Date(); // allocates a Date objectconst e = document.createElement("div"); // allocates a DOM element

Some methods allocate new values or objects:

js
const s = "string";const s2 = s.substr(0, 3); // s2 is a new string// Since strings are immutable values,// JavaScript may decide to not allocate memory,// but just store the [0, 3] range.const a = ["yeah yeah", "no no"];const a2 = ["generation", "no no"];const a3 = a.concat(a2);// new array with 4 elements being// the concatenation of a and a2 elements.

Using values

Using values basically means reading and writing in allocated memory. This can be done by reading or writing the value of a variable or an object property or even passing an argument to a function.

Release when the memory is not needed anymore

The majority of memory management issues occur at this phase. The most difficult aspect of this stage is determining when the allocated memory is no longer needed.

Low-level languages require the developer to manually determine at which point in the program the allocated memory is no longer needed and to release it.

Some high-level languages, such as JavaScript, utilize a form of automatic memory management known asgarbage collection (GC). The purpose of a garbage collector is to monitor memory allocation and determine when a block of allocated memory is no longer needed and reclaim it. This automatic process is an approximation since the general problem of determining whether or not a specific piece of memory is still needed isundecidable.

Garbage collection

As stated above, the general problem of automatically finding whether some memory "is not needed anymore" is undecidable. As a consequence, garbage collectors implement a restriction of a solution to the general problem. This section will explain the concepts that are necessary for understanding the main garbage collection algorithms and their respective limitations.

References

The main concept that garbage collection algorithms rely on is the concept ofreference. Within the context of memory management, an object is said to reference another object if the former has access to the latter (either implicitly or explicitly). For instance, a JavaScript object has a reference to itsprototype (implicit reference) and to its properties values (explicit reference).

In this context, the notion of an "object" is extended to something broader than regular JavaScript objects and also contain function scopes (or the global lexical scope).

Reference-counting garbage collection

Note:No modern JavaScript engine uses reference-counting for garbage collection anymore.

This is the most naïve garbage collection algorithm. This algorithm reduces the problem from determining whether or not an object is still needed to determining if an object still has any other objects referencing it. An object is said to be "garbage", or collectible if there are zero references pointing to it.

For example:

js
let x = {  a: {    b: 2,  },};// 2 objects are created. One is referenced by the other as one of its properties.// The other is referenced by virtue of being assigned to the 'x' variable.// Obviously, none can be garbage-collected.let y = x;// The 'y' variable is the second thing that has a reference to the object.x = 1;// Now, the object that was originally in 'x' has a unique reference// embodied by the 'y' variable.let z = y.a;// Reference to 'a' property of the object.// This object now has 2 references: one as a property,// the other as the 'z' variable.y = "mozilla";// The object that was originally in 'x' has now zero// references to it. It can be garbage-collected.// However its 'a' property is still referenced by// the 'z' variable, so it cannot be freed.z = null;// The 'a' property of the object originally in x// has zero references to it. It can be garbage collected.

There is a limitation when it comes to circular references. In the following example, two objects are created with properties that reference one another, thus creating a cycle. They will go out of scope after the function call has completed. At that point they become unneeded and their allocated memory should be reclaimed. However, the reference-counting algorithm will not consider them reclaimable since each of the two objects has at least one reference pointing to them, resulting in neither of them being marked for garbage collection. Circular references are a common cause of memory leaks.

js
function f() {  const x = {};  const y = {};  x.a = y; // x references y  y.a = x; // y references x  return "azerty";}f();

Mark-and-sweep algorithm

This algorithm reduces the definition of "an object is no longer needed" to "an object is unreachable".

This algorithm assumes the knowledge of a set of objects calledroots. In JavaScript, the root is the global object. Periodically, the garbage collector will start from these roots, find all objects that are referenced from these roots, then all objects referenced from these, etc. Starting from the roots, the garbage collector will thus find allreachable objects and collect all non-reachable objects.

This algorithm is an improvement over the previous one since an object having zero references is effectively unreachable. The opposite does not hold true as we have seen with circular references.

Currently, all modern engines ship a mark-and-sweep garbage collector. All improvements made in the field of JavaScript garbage collection (generational/incremental/concurrent/parallel garbage collection) over the last few years are implementation improvements of this algorithm, but not improvements over the garbage collection algorithm itself nor its reduction of the definition of when "an object is no longer needed".

The immediate benefit of this approach is that cycles are no longer a problem. In the first example above, after the function call returns, the two objects are no longer referenced by any resource that is reachable from the global object. Consequently, they will be found unreachable by the garbage collector and have their allocated memory reclaimed.

However, the inability to manually control garbage collection remains. There are times when it would be convenient to manually decide when and what memory is released. In order to release the memory of an object, it needs to be made explicitly unreachable. It is also not possible to programmatically trigger garbage collection in JavaScript — and will likely never be within the core language, although engines may expose APIs behind opt-in flags.

Configuring an engine's memory model

JavaScript engines typically offer flags that expose the memory model. For example, Node.js offers additional options and tools that expose the underlying V8 mechanisms for configuring and debugging memory issues. This configuration may not be available in browsers, and even less so for web pages (via HTTP headers, etc.).

The max amount of available heap memory can be increased with a flag:

bash
node --max-old-space-size=6000 index.js

We can also expose the garbage collector for debugging memory issues using a flag and theChrome Debugger:

bash
node --expose-gc --inspect index.js

Data structures aiding memory management

Although JavaScript does not directly expose the garbage collector API, the language offers several data structures that indirectly observe garbage collection and can be used to manage memory usage.

WeakMaps and WeakSets

WeakMap andWeakSet are data structures whose APIs closely mirror their non-weak counterparts:Map andSet.WeakMap allows you to maintain a collection of key-value pairs, whileWeakSet allows you to maintain a collection of unique values, both with performant addition, deletion, and querying.

WeakMap andWeakSet got the name from the concept ofweakly held values. Ifx is weakly held byy, it means that although you can access the value ofx viay, the mark-and-sweep algorithm won't considerx as reachable if nothing elsestrongly holds to it. Most data structures, except the ones discussed here, strongly holds to the objects passed in so that you can retrieve them at any time. The keys ofWeakMap andWeakSet can be garbage-collected (forWeakMap objects, the values would then be eligible for garbage collection as well) as long as nothing else in the program is referencing the key. This is ensured by two characteristics:

  • WeakMap andWeakSet can only store objects or symbols. This is because only objects are garbage collected — primitive values can always be forged (that is,1 === 1 but{} !== {}), making them stay in the collection forever.Registered symbols (likeSymbol.for("key")) can also be forged and thus not garbage collectable, but symbols created withSymbol("key") are garbage collectable.Well-known symbols likeSymbol.iterator come in a fixed set and are unique throughout the lifetime of the program, similar to intrinsic objects such asArray.prototype, so they are also allowed as keys.
  • WeakMap andWeakSet are not iterable. This prevents you from usingArray.from(map.keys()).length to observe the liveliness of objects, or get hold of an arbitrary key which should otherwise be eligible for garbage collection. (Garbage collection should be as invisible as possible.)

In typical explanations ofWeakMap andWeakSet (such as the one above), it's often implied that the key is garbage-collected first, freeing the value for garbage collection as well. However, consider the case of the value referencing the key:

js
const wm = new WeakMap();const key = {};wm.set(key, { key });// Now `key` cannot be garbage collected,// because the value holds a reference to the key,// and the value is strongly held in the map!

Ifkey is stored as an actual reference, it would create a cyclic reference and make both the key and value ineligible for garbage collection, even when nothing else referenceskey — because ifkey is garbage collected, it means that at some particular instant,value.key would point to a non-existent address, which is not legal. To fix this, the entries ofWeakMap andWeakSet aren't actual references, butephemerons, an enhancement to the mark-and-sweep mechanism.Barros et al. offers a good summary of the algorithm (page 4). To quote a paragraph:

Ephemerons are a refinement of weak pairs where neither the key nor the value can be classified as weak or strong. The connectivity of the key determines the connectivity of the value, but the connectivity of the value does not affect the connectivity of the key. […] when the garbage collection offers support to ephemerons, it occurs in three phases instead of two (mark and sweep).

As a rough mental model, think of aWeakMap as the following implementation:

Warning:This is not a polyfill nor is anywhere close to how it's implemented in the engine (which hooks into the garbage collection mechanism).

js
class MyWeakMap {  #marker = Symbol("MyWeakMapData");  get(key) {    return key[this.#marker];  }  set(key, value) {    key[this.#marker] = value;  }  has(key) {    return this.#marker in key;  }  delete(key) {    delete key[this.#marker];  }}

As you can see, theMyWeakMap never actually holds a collection of keys. It simply adds metadata to each object being passed in. The object is then garbage-collectable via mark-and-sweep. Therefore, it's not possible to iterate over the keys in aWeakMap, nor clear theWeakMap (as that also relies on the knowledge of the entire key collection).

For more information on their APIs, see thekeyed collections guide.

WeakRefs and FinalizationRegistry

Note:WeakRef andFinalizationRegistry offer direct introspection into the garbage collection machinery.Avoid using them where possible because the runtime semantics are almost completely unguaranteed.

All variables with an object as value are references to that object. However, such references arestrong — their existence would prevent the garbage collector from marking the object as eligible for collection. AWeakRef is aweak reference to an object that allows the object to be garbage collected, while still retaining the ability to read the object's content during its lifetime.

One use case forWeakRef is a cache system which maps string URLs to large objects. We cannot use aWeakMap for this purpose, becauseWeakMap objects have theirkeys weakly held, but not theirvalues — if you access a key, you would always deterministically get the value (since having access to the key means it's still alive). Here, we are okay to getundefined for a key (if the corresponding value is no longer alive) since we can just re-compute it, but we don't want unreachable objects to stay in the cache. In this case, we can use a normalMap, but with each value being aWeakRef of the object instead of the actual object value.

js
function cached(getter) {  // A Map from string URLs to WeakRefs of results  const cache = new Map();  return async (key) => {    if (cache.has(key)) {      const dereferencedValue = cache.get(key).deref();      if (dereferencedValue !== undefined) {        return dereferencedValue;      }    }    const value = await getter(key);    cache.set(key, new WeakRef(value));    return value;  };}const getImage = cached((url) => fetch(url).then((res) => res.blob()));

FinalizationRegistry provides an even stronger mechanism to observe garbage collection. It allows you to register objects and be notified when they are garbage collected. For example, for the cache system exemplified above, even when the blobs themselves are free for collection, theWeakRef objects that hold them are not — and over time, theMap may accumulate a lot of useless entries. Using aFinalizationRegistry allows one to perform cleanup in this case.

js
function cached(getter) {  // A Map from string URLs to WeakRefs of results  const cache = new Map();  // Every time after a value is garbage collected, the callback is  // called with the key in the cache as argument, allowing us to remove  // the cache entry  const registry = new FinalizationRegistry((key) => {    // Note: it's important to test that the WeakRef is indeed empty.    // Otherwise, the callback may be called after a new object has been    // added with this key, and that new, alive object gets deleted    if (!cache.get(key)?.deref()) {      cache.delete(key);    }  });  return async (key) => {    if (cache.has(key)) {      return cache.get(key).deref();    }    const value = await getter(key);    cache.set(key, new WeakRef(value));    registry.register(value, key);    return value;  };}const getImage = cached((url) => fetch(url).then((res) => res.blob()));

Due to performance and security concerns, there is no guarantee of when the callback will be called, or if it will be called at all. It should only be used for cleanup — and non-critical cleanup. There are other ways for more deterministic resource management, such astry...finally, which will always execute thefinally block.WeakRef andFinalizationRegistry exist solely for optimization of memory usage in long-running programs.

For more information on the API ofWeakRef andFinalizationRegistry, see their reference pages.

Help improve MDN

Learn how to contribute.

This page was last modified on byMDN contributors.


[8]ページ先頭

©2009-2025 Movatter.jp