【问题标题】:Garbage-collected cache via Javascript WeakMaps通过 Javascript WeakMaps 收集垃圾缓存
【发布时间】:2014-10-23 10:10:56
【问题描述】:

我想在 JavaScript 中缓存大对象。这些对象是通过键检索的,缓存它们是有意义的。但它们不会一次全部放入内存中,所以我希望在需要时对它们进行垃圾收集 - GC 显然知道得更好。

使用其他语言中的WeakReferenceWeakValueDictionary 制作这样的缓存非常简单,但在ES6 中我们使用WeakMap 代替,其中 很弱。

那么,是否有可能制作类似 WeakReference 的东西或从 WeakMap 制作垃圾收集缓存?

【问题讨论】:

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


【解决方案1】:

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

AFAIK 对这两个问题的回答都是“否”。

【讨论】:

    【解决方案2】:

    在两种情况下,哈希映射变弱是有用的(你的似乎适合第二种情况):

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

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

    在这两种情况下,只要表中的引用有用,它们就会一直保持活动状态,并且当它们变得无用时,“自然”就会有资格被收集。不幸的是,WeakReference 的设计者并没有为上面定义的两种用法实现单独的类,而是让它可以用于任何一种,尽管不是非常好。

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

    【讨论】:

    • 我认为这个问题专门针对 JavaScript,而不是 Java 或其他具有 WeakReference/WeakHashMap 的语言。问题是 JavaScript 只有 WeakHashMap 之类的东西,而不是 WeakReference 的等价物。
    • @Qantas94Heavy:确实如此。也许我应该指出 JavaScript 只支持第一个,不幸的是,这不是我怀疑 OP 真正想要的。你喜欢这个编辑吗?
    【解决方案3】:

    正如其他答案所提到的,不幸的是,没有弱映射之类的东西,就像在 Java / C# 中那样。

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

    1. 在必要时始终删除访问最少的对象
    2. 不要造成内存泄漏。

    这是代码。

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

    【讨论】:

      猜你喜欢
      • 2012-02-09
      • 1970-01-01
      • 2011-01-04
      • 1970-01-01
      • 2012-11-30
      • 1970-01-01
      • 1970-01-01
      • 2012-12-28
      • 2011-01-21
      相关资源
      最近更新 更多