javascriptgarbage-collection

Does JavaScript garbage collection delete parent objects if sub object is still in scope?


If I have an object that contains 2 sub-objects, and I delete the pointers to the parent object and one of the child objects, but I still have a pointer to the other child object, will JavaScript delete the one child and the parent, or will the whole parent object remain even though it's no longer addressable?

I know I could use delete to remove the other child element, but that still leaves the parent. And I want to understand GC better and whether this explicit cleanup is necessary.

var parentObj = {
    A: { subObjA: 'data' },
    B: { subObjB: 'data' }
};

var persist = parentObj.B;
parentObj = null;

After this, will the parent object and subObjA be deleted since they are no longer accessible. or will the whole object remain because I have a pointer to subObjB?


Solution

  • The parent and sibling will be garbage collected (in modern Node and V8 implementations). Because the object at parentObj.A has no reference to parentObj itself, the Mark-and-Sweep algorithm determines (correctly) that it is no longer worth keeping around.

    You can test this using WeakRefs or FinalizationRegistry and forcing garbage collection with Node's gc() function or Chrome's devtools. WeakRefs allow you to maintain access to an object without affecting the garbage collector's behavior, and FinalizationRegistry will call a provided callback when an object is collected.

    const registry = new FinalizationRegistry(label => console.log(label, "has been collected"));
    
    var parentObj = {
        A: { subObjA: 'data' },
        B: { subObjB: 'data' }
    };
    var childA = parentObj.A
        
    registry.register(parentObj, 'parent');
    registry.register(parentObj.A, 'childA');
    registry.register(parentObj.B, 'childB');
    
    parentObj = null;
    

    Output

    childB has been collected
    parent has been collected
    

    Or, if you prefer weak references

    var parentObj = {
        A: { subObjA: 'data' },
        B: { subObjB: 'data' }
    };
    
    var weakParent = new WeakRef(parentObj)
    var weakChildA = new WeakRef(parentObj.A)
    var weakChildB = new WeakRef(parentObj.B)
    
    var strongChildA = parentObj.A
    
    parentObj = null
    
    // <Collect Garbage>
    
    weakParent.deref() // undefined
    weakChildB.deref() // undefined
    weakChildA.deref() // {"subObjA": "data"}