How to detect if an object has been garbage collected in Javascript
WeakMap
At first, you may think that WeakMap will do it. WeakMap/WeakSet will hold onto things for you, but don't prevent an object from being garbage collected. The instant an object is GC'd, it is removed from the WeakMap or WeakSet.So, the obvious solution is to check if an object is still inside the WeakMap. If it is missing, it has been GC'd. This won't work.
The problem is that WeakMap and WeakSet are designed so that you cannot get at what's inside without already knowing it is there. In order to lookup an item, you need to already have that item. These collections don't even have a length method.
To check if an object is inside of a WeakMap, you must already have a reference to it, and therefore you are preventing it from being garbage collected.
So, what good are they? WeakMap is best used to link objects together. For example, if you have a bunch of <img> elements and you want to associate some data with them, you could simply do img.myextraproperty="blah". But your IDE may complain because the HTMLImageElement does not have this property. Instead, you can use WeakMap. If the extra property is a single value true then use a WeakSet.
The Real Solution
Some browsers, including Chrome but not Firefox, have the ability to check the amount of Javascript memory used. So the solution to test if an object is there, is to make it sufficiently large so that it has a noticeable impact on memory.In the code below, I use a WeakMap to associate a 1 gigabyte object with whatever you pass in. When the object is freed, and the garbage collector is run, you would expect at least 1 GB of memory to be freed as well. That is what the code checks for. The process takes at least 10 seconds, because it seems that Chrome only runs the garbage collector every 10 seconds.
/** Determines if an object is freed @param obj is the object of interest @param freeFn is a function that frees the object. @returns a promise that resolves to {freed: boolean, memoryDiff:number} @author Steve Hanov <steve.hanov@gmail.com> */ function isObjectFreed(obj, freeFn) { return new Promise( (resolve) => { if (!performance.memory) { throw new Error("Browser not supported."); } // When obj is GC'd, the large array will also be GCd and the impact will // be noticeable. const allocSize = 1024*1024*1024; const wm = new WeakMap([[obj, new Uint8Array(allocSize)]]); // wait for memory counter to update setTimeout( () => { const before = performance.memory.usedJSHeapSize; // Free the memory freeFn(); // wait for GC to run, at least 10 seconds setTimeout( () => { const diff = before - performance.memory.usedJSHeapSize; resolve({ freed: diff >= allocSize, memoryDiff: diff - allocSize }); }, 10000); }, 100); }); } let foo = {bar:1}; isObjectFreed(foo, () => foo = null).then( (result) => { document.write(`Object GCd:${result.freed}, ${result.memoryDiff} bytes freed`) }, (error) => { document.write(`Error: ${error.message}`) })