Thursday, March 5, 2015

Trying to prove that WeakMap is actually weak

I don't have a ton of experience with weak maps, but I would expect the following to work:
// File: example.js
var key = {};
var indirectReference = {};
indirectReference['key'] = (function() { var m = {}; m['foo'] = 'bar'; return m; })();
var map = new WeakMap();

map.set(key, indirectReference['key']);
console.log('Has key after setting value: %s', map.has(key));

delete indirectReference['key'];
console.log('Has key after deleting value: %s', map.has(key));

console.log('Has key after performing global.gc(): %s', map.has(key));
I downloaded the latest version of io.js (so that I could have a JavaScript runtime with WeakMap and global.gc() and ran it as follows:
./iojs --expose-gc example.js
Here is what I see:
Has key after setting value: true
Has key after deleting value: true
Has key after performing global.gc(): true
Despite my best efforts, I can't seem to get WeakMap to give up the value that is mapped to key. Am I doing it wrong? Obviously I'm making some assumptions here, so I'm curious where I'm off.

Ultimately, I would like to be able to use WeakMap to write some tests to ensure certain objects get garbage collected and don't leak memory.


  1. In the JS weakmap implementation, the keys are held by weak references, not the values? So, like, if you want to associate an Element with some Object, but you want that Object to go away if the Element goes away.

    (I think the API is actually specifically designed to not allow the use case you have in mind, where you're monitoring the gc, but I don't know the background reasoning behind that)

  2. Google Ephemeron. That's what a WeakMap is.

    It's just a way of "attaching" hidden properties to the keys. When you have the map and the key you can get hold of a property, the value. When either the key or the map are GCed, the value will not be kept alive any more.

    You could use ES6 symbols for the same thing, where the symbol plays the role of the WeakMap, and you use the symbol to put a property on the key:

    // Solution with WeakMap. If either the map or the key are unreachable then
    // the value is also unreachable (unless it's reachable some other way).
    var map = new WeakMap();
    map[key] = value

    // Solution with Symbol. If the key becomes unreachable then the value can
    // become unreachable, but if you have the key you can always get the symbol
    // using getOwnPropertySymbols, so the value cannot be collected unless the
    // key is collected.
    var map = Symbol(); // Creates a new primitive symbol object
    key[symbol] = value;

    You were probably expecting WeakMap to behave like WeakReference from Java. That would mean that the JS program could detect when a GC had taken place, which would inevitably introduce compatibility issues between the JS engines. This is probably one of the reasons it hasn't happened.

  3. Nick and Erik, yep, you're right, I was assuming WeakMap was going to behave like WeakReference from Java, so that's the source of my problems.

    I suspect the only way to do what I want is to explicitly expose this functionality from V8/Node. Obviously that would introduce an incompatibility across JS engines, but my intention is to use it only for memory leak tests, so I could live with that.