垃圾收集缓存通过Javascript WeakMaps

时间:2014-08-29 11:40:49

标签: javascript caching garbage-collection ecmascript-6 ecmascript-harmony

我想在JS中缓存大对象。这些对象是按键检索的,缓存它们是有意义的。但是它们一下子就不适合记忆,所以如果需要的话我希望它们能够被垃圾收集 - GC显然知道的更好。

使用其他语言中的WeakReference或WeakValueDictionary来创建这样的缓存非常简单,但在ES6中我们有WeakMap,而 keys 则很弱。

那么,是否可以制作类似WeakReference的内容或从WeakMap制作垃圾收集的缓存?

3 个答案:

答案 0 :(得分:5)

有两种情况可能会使哈希映射变弱(您的哈希值似乎适合第二种情况):

  1. 希望将信息附加到具有已知身份的对象;如果对象不再存在,附加的信息将变得毫无意义,并且同样应该不再存在。 JavaScript支持这种情况。

  2. 为了降低存储要求和加快比较,希望将引用合并到语义相同的对象。将大量引用替换为相同的大子树,例如,通过引用相同的子树,可以允许减少内存使用和执行时间的数量级。不幸的是,JavaScript并不支持这种情况。

  3. 在这两种情况下,表格中的引用只要它们有用就会保持活着,并且自然而然地会#34;当它们变得无用时就有资格收集。不幸的是,WeakReference的设计者不是为上面定义的两个用法实现单独的类,而是使它可以有点可用,但不是很好。

    如果键将相等性定义为表示引用标识,则WeakHashMap将满足第一个使用模式,但第二个将无意义(代码保存对与存储键在语义上相同的对象的引用将保存对存储的键的引用,并且不需要WeakHashMap给它一个)。在密钥定义其他形式的相等的情况下,对于表查询返回除了对存储对象的引用之外的任何内容通常没有意义,但是避免使存储引用使密钥保持活动的唯一方法是使用WeakHashMap<TKey,WeakReference<TKey>>并让客户端检索弱引用,检索存储在其中的密钥引用,并检查它是否仍然有效(它可以在WeakHashMap返回之间收集WeakReference以及检查WeakReference本身的时间。)

答案 1 :(得分:2)

  

是否可以从WeakMap制作WeakReference或从WeakMap制作垃圾收集缓存?

AFAIK答案是&#34;没有&#34;两个问题。

答案 2 :(得分:0)

正如其他答案所提到的那样,遗憾的是,没有弱地图这样的东西,就像Java / C#中那样。

作为一种解决方法,我创建了这个CacheMap来保持最大数量的对象,并在一段时间内跟踪它们的使用情况,以便您:

  1. 必要时始终删除访问量最少的对象
  2. 不要造成内存泄漏。
  3. 这是代码。

    "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's 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;
      }
    };
    

    你这样称呼它:

    // 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);