Search code examples
memory-managementgarbage-collectionv8

Memory Management in Javascript


Have a look at the code. Lets assume each statement takes 0 milliseconds to complete. printAfter2 is a simple function that prints the string passed to it after the 2 seconds of the call.

printAfter2 = (obj) => {
    setTimeout(() => {
        console.log(JSON.stringify(obj));
    }, 2000)
} 

In this below code we created a function that

  • defines a block-scoped variable obj at time 0 ms

  • calls the function with obj (type is Object) as parameter at time 0 ms. Since the passed parameter is an Object so its reference will be passed to the function.

  • Then there is console.log function call. After it the block ends at time 0 ms so the block scoped variable obj will also be destroyed.

  • At time 2000, the printAfter2 function fetches the value of parameter which was passed to it. In this case it is a reference of a variable which should be destroyed so far. But this didn't work as expected. It prints the same original obj at 2000 ms which was supposed to be destroyed at 0 ms. Why is this so?

We actually need not a async function but ignore it.

(async () => {
    let obj = {name: 'Ali'}
    printAfter2(obj);
    console.log("obj var will be destroyed after this block");
})()

Solution

  • When the variable/parameter obj goes out of scope, that doesn't mean that anything gets destroyed immediately. It only means that one reference to some object disappears, which makes this object eligible for garbage collection if and only if that was the last reference to it. The garbage collector will eventually (next time it runs) free memory belonging to objects that are no longer reachable, i.e. have no references to them. Let's look at a simpler case, without any closures:

    let o1;
    function f1(obj) {
      console.log(obj);  // (3)
    }                    // (4)
    o1 = new Object();   // (1)
    f1(o1);              // (2)
    let o2 = o1;         // (5)
    o1 = null;           // (6)
    // (7)
    o2 = new Array();
    // (8)
    

    Line (1) obviously allocates an Object, and uses the variable o1 to refer to it. Note that there is a distinction between the object and the variable; in particular they have different lifetimes. Line (2) passes the Object to the function; while the function executes (e.g. in line (3)), there are two variables referring to the same object: o1 in the outer scope, and obj in f1's scope. When f1 terminates in line (4), the variable obj goes out of scope, but the Object is still reachable via o1. Line (5) creates a new variable, again referring to the same object. This is conceptually very similar to passing it to some function. When o1 stops referring to the Object in line (6), that doesn't make the Object eligible for garbage collection in line (7), because o2 is still referring to it ("keeping it alive"). Only once o2 is also reassigned, or goes out of scope, does the Object become unreachable: if the garbage collector runs any time after execution has reached line (8), the Object's memory will be freed.

    (Side note: the garbage collector doesn't actually "collect garbage" or "destroy objects", because it doesn't touch that memory at all. It only registers the fact that the memory where the Object was stored is now free to be used for a new allocation.)

    In the case of your example, you're creating a closure () => console.log(JSON.stringify(obj)) that contains a reference to the object. While this closure sits around waiting for its time to execute, this reference will keep the object alive. It can only be freed after the closure has run its course, and has become unreachable itself.

    To illustrate in a different way:

    function MakeClosure() {
      let obj = {message: "Hello world"};
      return function() { console.log(JSON.stringify(obj)); };
    }
    
    let callback = MakeClosure();
    // While the local variable `obj` is inaccessible now, `callback` internally
    // has a reference to the object created as `{message: ...}`.
    setTimeout(callback, 2000);
    // Same situation as above at this point.
    callback = null;
    // Now the variable `callback` can't be used any more to refer to the closure,
    // but the `setTimeout` call added the closure to some internal list, so it's
    // not unreachable yet.
    // Only once the callback has run and is dropped from the engine-internal list
    // of waiting setTimeout-scheduled callbacks, can the `{message: ...}` object get
    // cleaned up -- again, this doesn't happen immediately, only whenever the garbage 
    // collector decides to run.