解释起来有点棘手,但我会试一试:
在node.js服务器应用程序中,我想处理一次可以在多个地方使用的数据对象。主要问题是,这些对象仅由对象id引用,并从数据库加载。
但是,只要一个对象已经加载到一个作用域中,就不应该在请求时第二次加载它,而是应该返回相同的对象。
这引出了垃圾收集的问题:只要在任何范围内不再需要对象,就应该完全释放它以防止整个数据库一直在服务器的内存中。但是这里开始出现问题:
我可以通过两种方式来创建这样的场景:使用全局对象引用(可以防止收集任何对象),或者真正复制这些对象但是以每次属性的方式同步它们一个范围被更改,通知其他实例有关该更改。
同样,因此每个实例都必须注册一个事件处理程序,而事件处理程序又指向该实例,从而阻止它再次被收集。
有没有人想出一个解决方案,我只是没有意识到这种情况?或者我对垃圾收集器的理解有什么误解吗?
我想要避免的是对内存中每个对象的手动引用计数。每次从任何集合中删除对象时,我都必须手动调整引用计数(在js中甚至没有析构函数或“引用减少”事件)
答案 0 :(得分:2)
听起来像Map
对象的作业,该对象用作存储对象的缓存作为值(连同计数)和ID作为键。如果需要对象,首先要在Map
中查找其ID。如果在那里找到它,则使用返回的对象(将由所有人共享)。如果在那里找不到它,则从数据库中取出它并将其插入Map
(供其他人查找)。
然后,为了使Map
不会永远增长,从Map
获取内容的代码也需要从Map
中释放一个对象。当useCnt在发布时变为零时,您将从Map
中删除对象。
通过创建某种包含cache
的{{1}}对象并具有获取对象或释放对象的方法,可以使调用者完全透明,这将完全负责维护Map
中每个对象的refCnt
。
注意:您可能必须编写从数据库中获取它的代码并将其小心地插入Map
以便不创建竞争条件,因为数据库的提取可能是异步的,您可以让多个调用者都在Map
中找不到它,并且在从数据库中获取它的所有过程中都是如此。如何避免这种竞争条件取决于您拥有的确切数据库以及您如何使用它。一种可能性是第一个调用者在Map
中插入一个占位符,因此后续调用者将知道在将对象插入Map
并且可供他们使用之前等待某个承诺解析。
以下是关于Map
如何运作的一般概念。当您想要检索项目时,请致电ObjCache
。这总是返回一个解析为对象的promise(如果从DB中获取错误,则拒绝)。如果对象已经在缓存中,则它返回的promise将已经解析。如果对象尚未在缓存中,则从数据库中提取时,promise将解析。即使代码的多个部分请求一个“正在从DB中获取”的对象,这也可以工作。当从数据库中检索到对象时,它们都获得了使用相同对象解析的相同承诺。每次调用cache.get(id)
都会增加缓存中该对象的cache.get(id)
。
然后在使用对象完成给定代码时调用refCnt
。如果cache.release(id)
达到零,那将减少内部refCnt
并从缓存中删除对象。
refCnt
答案 1 :(得分:2)
使用weak
模块,我实施了一个WeakMapObj
,其工作方式与我们原来希望WeakMap
一样有效。它允许您使用键的基元和数据的对象,并使用弱引用保留数据。并且,当它们的数据被GC时,它会自动从地图中删除项目。事实证明这很简单。
const weak = require('weak');
class WeakMapObj {
constructor(iterable) {
this._map = new Map();
if (iterable) {
for (let array of iterable) {
this.set(array[0], array[1]);
}
}
}
set(key, obj) {
if (typeof obj === "object") {
let ref = weak(obj, this.delete.bind(this, key));
this._map.set(key, ref);
} else {
// not an object, can just use regular method
this._map.set(key, obj);
}
}
// get the actual object reference, not just the proxy
get(key) {
let obj = this._map.get(key);
if (obj) {
return weak.get(obj);
} else {
return obj;
}
}
has(key) {
return this._map.has(key);
}
clear() {
return this._map.clear();
}
delete(key) {
return this._map.delete(key);
}
}
我能够在测试应用程序中测试它并确认它在垃圾收集器运行时按预期工作。仅供参考,只需使一个或两个对象符合垃圾收集条件,就不会导致垃圾收集器在我的测试应用程序中运行。我不得不强行调用垃圾收集器来查看效果。我认为这不是真正的应用程序中的问题。 GC将在需要时运行(只有在需要做大量合理工作时才会运行。)
您可以将此更通用的实现用作对象缓存的核心,其中项目将仅保留在WeakMapObj
中,直到其他地方不再引用它为止。
这是一个实现,它使地图完全保密,因此无法从WeakMapObj
方法之外访问它。
const weak = require('weak');
function WeakMapObj(iterable) {
// private instance data
const map = new Map();
this.set = function(key, obj) {
if (typeof obj === "object") {
// replace obj with a weak reference
obj = weak(obj, this.delete.bind(this, key));
}
map.set(key, obj);
}
// add methods that have access to "private" map
this.get = function(key) {
let obj = map.get(key);
if (obj) {
obj = weak.get(obj);
}
return obj;
}
this.has = function(key) {
return map.has(key);
}
this.clear = function() {
return map.clear();
}
this.delete = function(key) {
return map.delete(key);
}
// constructor implementation
if (iterable) {
for (let array of iterable) {
this.set(array[0], array[1]);
}
}
}
答案 2 :(得分:1)
好的,对于任何遇到类似问题的人,我找到了解决方案。 jfriend00通过提及WeakMaps
这个解决方案推动了我这个解决方案本身并不完全是解决方案,但我将注意力集中在弱引用上。
有一个简称为weak
的npm模块可以解决问题。它包含对象的弱引用,并在对象被垃圾回收后安全地返回空对象(因此,有一种方法可以识别收集的对象)。
所以我使用WeakCache
创建了一个名为DataObject
的类:
class DataObject{
constructor( objectID ){
this.objectID = objectID;
this.dataLoaded = new Promise(function(resolve, reject){
loadTheDataFromTheDatabase(function(data, error){ // some pseudo db call
if (error)
{
reject(error);
return;
}
resolve(data);
});
});
}
loadData(){
return this.dataLoaded;
}
}
class WeakCache{
constructor(){
this.cache = {};
}
getDataObjectAsync( objectID, onObjectReceived ){
if (this.cache[objectID] === undefined || this.cache[objectID].loadData === undefined){ // object was not cached yet or dereferenced, recreate it
this.cache[objectID] = weak(new DataObject( objectID )function(){
// Remove the reference from the cache when it got collected anyway
delete this.cache[this.objectID];
}.bind({cache:this, objectID:objectID});
}
this.cache[objectID].loadData().then(onObjectReceived);
}
}
这个课程仍在进行中,但至少这是一个可行的方法。这是唯一的缺点(但对所有基于数据库的数据都是如此,双关语警报!因此并不是一件大事),所有数据访问都必须是异步的。
这里会发生的是,某些点的缓存可能包含对每个可能的对象id的空引用。