Tuesday, May 26, 2020

How to detect if an object has been garbage collected in JavaScript

If you are writing an application in Javascript, soon you will have to worry about memory leaks. But it is difficult to even know if a memory leak exists. This handy method can help.

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.

View on CodePen

 /** 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 &ltsteve.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}`) }) 

How I use it

I use this method as part of my test suite for

Zwibbler

, my Javascript drawing app. It has a

destroy() method

that is supposed to remove all resources. But occasionally, I would have some event listener that was not removed, that would keep a reference to the entire application. So when using it inside something like React or Angular, where it can be repeatedly shown and hidden by the view framework, it is vitally important that resources be completely freed.



from Hacker News https://ift.tt/3gndWjU

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.