通过Javascript弱映射收集垃圾缓存

时间:2021-11-08 03:05:09

I want to cache large objects in JS. These objects are retrieved by key, and it makes sense to cache them. But they won't fit in memory all at once, so I want them to be garbage collected if needed - the GC obviously knows better.

我想用JS缓存大对象。这些对象由键检索,缓存它们是有意义的。但是它们不会同时装入内存,所以如果需要的话,我希望它们被垃圾收集——显然GC知道得更多。

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

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

So, is it possible to make something like a WeakReference or make garbage-collected caches from WeakMap?

那么,是否有可能制作一种类似于WeakReference的东西或者从WeakMap中收集垃圾?

3 个解决方案

#1


2  

is it possible to make WeakReference from WeakMap or make garbage-collected cache from WeakMap ?

是否可以从WeakMap中进行WeakReference或者从WeakMap中进行垃圾收集缓存?

AFAIK the answer is "no" to both questions.

两个问题的答案都是“不”。

#2


5  

There are two scenarios where it's useful for a hash map to be weak (yours seems to fit the second):

有两种情况下,散列映射弱是有用的(您的似乎适合第二种情况):

  1. One wishes to attach information to an object with a known identity; if the object ceases to exist, the attached information will become meaningless and should likewise cease to exist. JavaScript supports this scenario.

    希望将信息附加到具有已知身份的对象;如果物体停止存在,附加的信息将变得没有意义,同样也应该停止存在。JavaScript支持这个场景。

  2. One wishes to merge references to semantically-identical objects, for the purposes of reducing storage requirements and expediting comparisons. Replacing many references to identical large subtrees, for example, with references to the same subtree can allow order-of-magnitude reductions in memory usage and execution time. Unfortunately JavaScript doesn't support this scenario.

    为了减少存储需求和加速比较,我们希望合并对语义相同对象的引用。例如,将许多引用替换为相同的大型子树,引用相同的子树可以减少内存使用量和执行时间。不幸的是,JavaScript不支持这种场景。

In both cases, references in the table will be kept alive as long as they are useful, and will "naturally" become eligible for collection when they become useless. Unfortunately, rather than implementing separate classes for the two usages defined above, the designers of WeakReference made it so it can kinda-sorta be usable for either, though not terribly well.

在这两种情况下,只要表中的引用有用,它们就会一直存在,并且当它们变得无用时,将“自然”成为收集的合格对象。不幸的是,WeakReference的设计人员没有为上面定义的两个用法实现单独的类,而是让它可以同时适用于这两个用法,尽管不是很好。

In cases where the keys define equality to mean reference identity, WeakHashMap will satisfy the first usage pattern, but the second would be meaningless (code which held a reference to an object that was semantically identical to a stored key would hold a reference to the stored key, and wouldn't need the WeakHashMap to give it one). In cases where keys define some other form of equality, it generally doesn't make sense for a table query to return anything other than a reference to the stored object, but the only way to avoid having the stored reference keep the key alive is to use a WeakHashMap<TKey,WeakReference<TKey>> and have the client retrieve the weak reference, retrieve the key reference stored therein, and check whether it's still valid (it could get collected between the time the WeakHashMap returns the WeakReference and the time the WeakReference itself gets examined).

的情况下键定义平等意味着参考身份,WeakHashMap将满足第一个使用模式,但是第二将毫无意义(代码引用一个对象语义相同的存储密钥将引用存储密钥,而不需要WeakHashMap给它一个)。在键的情况下定义一些其他形式的平等,它通常没有意义的表的查询返回任何东西除了存储对象的引用,但避免存储引用的唯一方法保持活着的关键是使用WeakHashMap < TKey WeakReference引用< TKey > >客户端检索弱引用,检索引用存储在其中的关键并且检查它是否仍然有效(它可以在WeakHashMap返回弱引用和弱引用本身被检查的时间之间进行收集)。

#3


0  

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

正如前面提到的其他答案一样,不幸的是,没有像Java / c#中那样的弱映射。

As a work around, I created this CacheMap that keeps a maximum number of objects around, and tracks their usage over a set period of time so that you:

作为一项工作,我创建了这个CacheMap,它保存了最大数量的对象,并在一段时间内跟踪它们的使用情况,以便:

  1. Always remove the least accessed object, when necessary
  2. 在必要时,总是删除访问最少的对象
  3. Don't create a memory leak.
  4. 不要创建内存泄漏。

Here's 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's no way to create a weak map like in Java/C#.  
 * See https://*.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 like so:

你这样称呼它:

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

#1


2  

is it possible to make WeakReference from WeakMap or make garbage-collected cache from WeakMap ?

是否可以从WeakMap中进行WeakReference或者从WeakMap中进行垃圾收集缓存?

AFAIK the answer is "no" to both questions.

两个问题的答案都是“不”。

#2


5  

There are two scenarios where it's useful for a hash map to be weak (yours seems to fit the second):

有两种情况下,散列映射弱是有用的(您的似乎适合第二种情况):

  1. One wishes to attach information to an object with a known identity; if the object ceases to exist, the attached information will become meaningless and should likewise cease to exist. JavaScript supports this scenario.

    希望将信息附加到具有已知身份的对象;如果物体停止存在,附加的信息将变得没有意义,同样也应该停止存在。JavaScript支持这个场景。

  2. One wishes to merge references to semantically-identical objects, for the purposes of reducing storage requirements and expediting comparisons. Replacing many references to identical large subtrees, for example, with references to the same subtree can allow order-of-magnitude reductions in memory usage and execution time. Unfortunately JavaScript doesn't support this scenario.

    为了减少存储需求和加速比较,我们希望合并对语义相同对象的引用。例如,将许多引用替换为相同的大型子树,引用相同的子树可以减少内存使用量和执行时间。不幸的是,JavaScript不支持这种场景。

In both cases, references in the table will be kept alive as long as they are useful, and will "naturally" become eligible for collection when they become useless. Unfortunately, rather than implementing separate classes for the two usages defined above, the designers of WeakReference made it so it can kinda-sorta be usable for either, though not terribly well.

在这两种情况下,只要表中的引用有用,它们就会一直存在,并且当它们变得无用时,将“自然”成为收集的合格对象。不幸的是,WeakReference的设计人员没有为上面定义的两个用法实现单独的类,而是让它可以同时适用于这两个用法,尽管不是很好。

In cases where the keys define equality to mean reference identity, WeakHashMap will satisfy the first usage pattern, but the second would be meaningless (code which held a reference to an object that was semantically identical to a stored key would hold a reference to the stored key, and wouldn't need the WeakHashMap to give it one). In cases where keys define some other form of equality, it generally doesn't make sense for a table query to return anything other than a reference to the stored object, but the only way to avoid having the stored reference keep the key alive is to use a WeakHashMap<TKey,WeakReference<TKey>> and have the client retrieve the weak reference, retrieve the key reference stored therein, and check whether it's still valid (it could get collected between the time the WeakHashMap returns the WeakReference and the time the WeakReference itself gets examined).

的情况下键定义平等意味着参考身份,WeakHashMap将满足第一个使用模式,但是第二将毫无意义(代码引用一个对象语义相同的存储密钥将引用存储密钥,而不需要WeakHashMap给它一个)。在键的情况下定义一些其他形式的平等,它通常没有意义的表的查询返回任何东西除了存储对象的引用,但避免存储引用的唯一方法保持活着的关键是使用WeakHashMap < TKey WeakReference引用< TKey > >客户端检索弱引用,检索引用存储在其中的关键并且检查它是否仍然有效(它可以在WeakHashMap返回弱引用和弱引用本身被检查的时间之间进行收集)。

#3


0  

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

正如前面提到的其他答案一样,不幸的是,没有像Java / c#中那样的弱映射。

As a work around, I created this CacheMap that keeps a maximum number of objects around, and tracks their usage over a set period of time so that you:

作为一项工作,我创建了这个CacheMap,它保存了最大数量的对象,并在一段时间内跟踪它们的使用情况,以便:

  1. Always remove the least accessed object, when necessary
  2. 在必要时,总是删除访问最少的对象
  3. Don't create a memory leak.
  4. 不要创建内存泄漏。

Here's 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's no way to create a weak map like in Java/C#.  
 * See https://*.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 like so:

你这样称呼它:

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