Подтвердить что ты не робот

Сбор кэша с помощью мусора с помощью Javascript WeakMaps

Я хочу кэшировать большие объекты в JS. Эти объекты извлекаются ключом, и имеет смысл кэшировать их. Но они не будут вписываться в память сразу, поэтому я хочу, чтобы они были собраны в мусор, если это необходимо - GC, очевидно, знает лучше.

Весьма тривиально сделать такой кеш, используя WeakReference или WeakValueDictionary, найденный на других языках, но в ES6 вместо этого мы имеем WeakMap, где ключи слабы.

Итак, можно ли сделать что-то вроде WeakReference или сделать сборники с мусором из WeakMap?

4b9b3361

Ответ 1

Можно ли сделать WeakReference из WeakMap или сделать сборку мусора с WeakMap?

AFAIK ответ "нет" на оба вопроса.

Ответ 2

Существует два сценария, когда полезно, чтобы хэш-карта была слабой (кажется, что ваша вторая соответствует):

  • Один хочет прикрепить информацию к объекту с известным идентификатором; если объект прекращает свое существование, прилагаемая информация станет бессмысленной и также должна прекратиться. JavaScript поддерживает этот сценарий.

  • Желательно объединить ссылки на семантически идентичные объекты в целях сокращения требований к хранению и ускорения сравнений. Замена многих ссылок на одинаковые большие поддеревья, например, со ссылками на одно и то же поддерево, может позволить уменьшить порядок использования памяти и время выполнения. К сожалению, JavaScript не поддерживает этот сценарий.

В обоих случаях ссылки в таблице будут поддерживаться до тех пор, пока они будут полезны, и "естественно" станут доступными для сбора, когда они станут бесполезными. К сожалению, вместо того, чтобы реализовывать отдельные классы для двух применений, определенных выше, разработчики WeakReference сделали это так, чтобы он мог быть полезен для любого, хотя и не очень хорошо.

В тех случаях, когда ключи определяют равенство для среднего идентификатора ссылки, WeakHashMap будет удовлетворять первому шаблону использования, но второй будет бессмысленным (код, содержащий ссылку на объект, который был семантически идентичен сохраненному ключу, ссылку на сохраненный ключ, и не понадобится WeakHashMap, чтобы дать ему). В тех случаях, когда ключи определяют некоторую другую форму равенства, обычно не имеет смысла, чтобы запрос таблицы возвращал что-либо, кроме ссылки на сохраненный объект, но единственный способ избежать сохранения сохраненной ссылки сохранить ключ в используйте WeakHashMap<TKey,WeakReference<TKey>> и попросите клиента получить слабую ссылку, извлеките сохраненную в ней ссылку ключа и проверьте, действительно ли она действительна (она может быть собрана между моментами, когда WeakHashMap возвращает WeakReference и время WeakReference сам проверяется).

Ответ 3

Как упоминалось в других ответах, к сожалению, нет такой вещи, как слабая карта, как в Java/С#.

Как работа, я создал этот CacheMap, который хранит максимальное количество объектов и отслеживает их использование в течение заданного периода времени, чтобы вы:

  • Всегда удаляйте наименее доступный объект, если необходимо
  • Не создавайте утечку памяти.

Здесь код.

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