Bjorn Reppen
Bjorn Reppen

Reputation: 22789

How do I make use of weak references in javascript?

I have some very large objects that are used intensively, but occasionally in my Node.JS program. Loading these objects are expensive. In total they occupy more memory than I have in the system.

Is there any way to create a "weak reference" in JavaScript, so that the garbage collector will remove my objects when memory is low, and then I can check whether the object is and reload it again if it was garbage collected since my last access?

The particular use case I had in mind was cartographic reprojection and tiling of gigabytes of map imagery.

Upvotes: 2

Views: 1378

Answers (3)

jfriend00
jfriend00

Reputation: 707876

Is there any way to create a "weak reference" in Javascript, so that the garbage collector will remove my objects when memory is low?

No, Javascript does not have that.

I don't think a weakMap or weakSet will offer you what you're trying to do here. They don't do what you're asking for. Instead, they allow you to have a reference to something that will NOT prohibit garbage collection. But, if there are no other references to the data, then it will be garbage collected immediately the next time the gc runs. So, they won't keep the data around for awhile like you want. If you have any other reference to those objects (to keep them around), then they will never get garbage collected. Javascript doesn't offer a weak reference that is only garbage collected when memory starts to get full. Something is either eligible for garbage collection or it isn't. If it's eligible, it will be freed in the next GC pass.

It sounds like what you probably want is a memory cache. You could decide how large you want the cache to be and then keep items in the cache based on some strategy. The most common strategy is LRU (least recently used) where you kick an item out of the cache when you reach the cache size limit and you need to load a new item in the cache. With LRU, you keep track of when an item was last used from the cache and you kick out the oldest one. If you are trying to manage the cache to a memory usage size, you will have to have some scheme for estimating the memory usage of your objects in the cache.

Note that many databases will essentially offer you this functionality as a built-in feature since they will usually contain some sort of caching themselves so if you request an item from the database that you have recently requested, it probably comes from a read cache. You don't really say what your objects are so it's hard for us to suggest exactly how they could be used from a database.

Upvotes: 2

greim
greim

Reputation: 9437

JavaScript's WeakRef may support your use case, depending on specifics.

For example, suppose you have a class with a getter that returns a data object with a big enough memory footprint that you only have space for a few such objects simultaneously.

class Thing {
  getBigData() {
    let bigData = this.ref?.deref();

    if (bigData === undefined) {
      bigData = new BigData(); // <-- allocates many GB
      this.ref = new WeakRef(bigData) ;
    }

    return bigData;
  }
}
const thing = new Thing();

for (let i=0; i<1000; i++) {
  // guaranteed new allocation when i === 0
  // guaranteed no new allocation when i > 0
  thing.getBigData();
}

await nextTick();

// allocation is indeterministic after a tick.
// if you need a guarantee here, you'll have to
// manage it yourself
thing.getBigData();

Caveat: The below would still cause an out-of-memory error since items added or extracted from a WeakRef cannot be GC'd until the end of the current tick.

for (let i=0; i<1000; i++) {
  new Thing().getBigData();
}

Caveat: The below would still cause an out-of-memory error since the additional strong refs will retain the data, despite running in separate ticks.

let strongRefs = [];

for (let i=0; i<1000; i++) {
  await nextTick();
  strongRefs.push(new Thing().getBigData());
}

Upvotes: 1

trincot
trincot

Reputation: 350841

Since the introduction of WeakRef it is possible, but whether that is really useful is doubtful.

Here is a demo, but of course it depends on the workings of the garbage collector, so there is no guarantee when the objects will be garbage collected. The only thing that is guaranteed is that the garbage collection will not happen as long as the synchronous part of the script is running, so the first output will report all objects to be live, even though we don't have strong references to them anymore:

// Create lots of objects with an `i` property:
let weakrefs = Array.from({length: 100000}, (_, i) =>
    new WeakRef({ i })
);
// Let's poll now and then how many of them are still live...
loop(); // This runs synchronously, so it will report all of them to be live
function loop() { 
    let liveCount = weakrefs.reduce((count, weakref) => count + !!weakref.deref(), 0);
    console.log(liveCount + " objects are still live");
    setTimeout(loop, 1000); // Wait one second and repeat
}

Upvotes: 1

Related Questions