Garbage collection cache using Javascript WeakMaps

I want to cache large objects in JS. These objects are extracted with the key, and it makes sense to cache them. But they will not fit into the memory immediately, so I want them to be garbage collected if necessary - GC obviously knows better.

It is quite trivial to make such a cache using WeakReference or WeakValueDictionary found in other languages, but in ES6 we have instead WeakMap, where the keys are weak.

So, is it possible to do something like WeakReference or make garbage WeakMap from WeakMap ?

+10
javascript garbage-collection ecmascript-6 caching ecmascript-harmony
Aug 29 '14 at 11:40
source share
3 answers

Is it possible to make WeakReference from WeakMap or do garbage collection with WeakMap?

AFAIK the answer is no to both questions.

+2
Aug 29 '14 at 16:08
source share

There are two scenarios where it is useful for the hash map to be weak (it seems your second one matches):

  • One wants to attach information to an object with a known identifier; if the object ceases to exist, the attached information will become meaningless and should also cease. JavaScript supports this script.

  • It is desirable to combine references to semantically identical objects in order to reduce storage requirements and speed comparisons. Replacing many links with the same large subtrees, for example, with links to the same subtree, can reduce memory usage and execution time. Unfortunately, JavaScript does not support this scenario.

In both cases, the links in the table will be maintained as long as they are useful, and “naturally” become available for collection when they become useless. Unfortunately, instead of implementing separate classes for the two uses defined above, the WeakReference developers made it so that it could be useful for anyone, albeit not very well.

In those cases where the keys define equality for the middle link identifier, WeakHashMap will satisfy the first usage pattern, but the second will be meaningless (the code containing a link to an object that was semantically identical to the stored key, a link to the stored key, and WeakHashMap is not needed to give him). In cases where the keys define some other form of equality, it usually does not make sense for the table query to return anything other than a reference to the saved object, but the only way to avoid saving the saved link is to save the key in use WeakHashMap<TKey,WeakReference<TKey>> and ask the client to get a weak link, extract the key link stored in it and check if it is valid (it can be collected between the moments when WeakHashMap returns WeakReference and the WeakReference time itself is checked).

+4
Sep 02 '14 at 20:37
source share

As mentioned in other answers, unfortunately, there is no such thing as a weak map, as in Java / C #.

As a job, I created this CacheMap , which stores the maximum number of objects and tracks their usage for a given period of time, so that you:

  • Always delete the least accessible item if necessary.
  • Do not create a memory leak.

Here is the code.

 "use strict"; /** * This class keeps a maximum number of items, along with a count of items requested over the past X seconds. * * Unfortunately, in JavaScript, there no way to create a weak map like in Java/C#. * See https://stackoverflow.com/questions/25567578/garbage-collected-cache-via-javascript-weakmaps */ module.exports = class CacheMap { constructor(maxItems, secondsToKeepACountFor) { if (maxItems < 1) { throw new Error("Max items must be a positive integer"); } if (secondsToKeepACountFor < 1) { throw new Error("Seconds to keep a count for must be a positive integer"); } this.itemsToCounts = new WeakMap(); this.internalMap = new Map(); this.maxItems = maxItems; this.secondsToKeepACountFor = secondsToKeepACountFor; } get(key) { const value = this.internalMap.get(key); if (value) { this.itemsToCounts.get(value).push(CacheMap.getCurrentTimeInSeconds()); } return value; } has(key) { return this.internalMap.has(key); } static getCurrentTimeInSeconds() { return Math.floor(Date.now() / 1000); } set(key, value) { if (this.internalMap.has(key)) { this.internalMap.set(key, value); } else { if (this.internalMap.size === this.maxItems) { // Figure out who to kick out. let keys = this.internalMap.keys(); let lowestKey; let lowestNum = null; let currentTime = CacheMap.getCurrentTimeInSeconds(); for (let key of keys) { const value = this.internalMap.get(key); let totalCounts = this.itemsToCounts.get(value); let countsSince = totalCounts.filter(count => count > (currentTime - this.secondsToKeepACountFor)); this.itemsToCounts.set(value, totalCounts); if (lowestNum === null || countsSince.length < lowestNum) { lowestNum = countsSince.length; lowestKey = key; } } this.internalMap.delete(lowestKey); } this.internalMap.set(key, value); } this.itemsToCounts.set(value, []); } size() { return this.internalMap.size; } }; 

And you call it that:

 // Keeps at most 10 client databases in memory and keeps track of their usage over a 10 min period. let dbCache = new CacheMap(10, 600); 
0
Oct 30 '17 at 14:03
source share



All Articles