这里真正的问题是调用global.gc() 不会运行完整的GC pass。在我下面的测试中,只有当我允许 10 秒的空闲时间时,我才能获得完整的 GC。
以下是对您的特定代码的一些观察。如果我在 GC 中添加一个 await delay(5000) 暂停,那么您的对象在 while 循环之前仍然没有被 GC。但是,如果我添加两个await delay(5000) 语句或一个await delay(10000) 语句,它会在while 循环之前进行GC。因此,GC 显然对时间敏感,并且调用 global.gc() 显然不是完整的 GC 运行。例如,这是您的代码的一个版本,其中 weakref 被 GCed!
function delay(t, v) {
return new Promise(resolve => {
setTimeout(resolve, t, v);
});
}
async function run() {
const lookup = new Map();
let element = new Object({ id: "someid", data: {} });
lookup.set(element.id, new WeakRef(element));
console.dir(lookup.get("someid").deref());
// as expected output is { id: 'someid', data: {} }
element = null;
await delay(10000);
console.log(element);
// as expected output is null
// if above is delay(5000), then it logs "in while loop"
// if above is delay(10000), then it does NOT log "in while loop"
// so the amount of time is important to allow the GC to do its thing
while (lookup.get("someid").deref()) {
console.log("in while loop");
break;
}
console.dir(lookup.get("someid").deref());
}
run();
在我发现你的代码会延迟 GC 之前,我开始进行实验以查看 WeakRef 是否有效。这是显示的代码(具有允许完全 GC 的正确延迟),WeakRef 确实在节点 v14.15 中工作。
这是我的测试代码:
// to make memory usage output easier to read
function addCommas(str) {
var parts = (str + "").split("."),
main = parts[0],
len = main.length,
output = "",
i = len - 1;
while (i >= 0) {
output = main.charAt(i) + output;
if ((len - i) % 3 === 0 && i > 0) {
output = "," + output;
}
--i;
}
// put decimal part back
if (parts.length > 1) {
output += "." + parts[1];
}
return output;
}
function delay(t, v) {
return new Promise(resolve => {
setTimeout(resolve, t, v);
});
}
function logUsage() {
let usage = process.memoryUsage();
console.log(`heapUsed: ${addCommas(usage.heapUsed)}`);
}
const numElements = 10000;
const lenArrays = 10000;
async function run() {
const cache = new Map();
const holding = [];
function checkItem(n) {
let item = cache.get(n).deref();
console.log(item);
}
// fill all the arrays and the cache
// and put everything into the holding array too
let arr, element;
for (let i = 0; i < numElements; i++) {
arr = new Array(lenArrays);
arr.fill(i);
element = { id: i, data: arr };
// temporarily hold onto each element by putting a
// full reference (not a weakRef) into an array
holding.push(element);
// add a weakRef to the Map
cache.set(i, new WeakRef(element));
}
// clean up locals we don't need any more
element = array = null;
// should have a big Map holding lots of data
// all items should still be available
checkItem(numElements - 1);
logUsage();
await delay(5000);
logUsage();
// make whole holding array contents eligible for GC
holding.length = 0;
// pause for GC, then see if items are available
// and what memory usage is
await delay(5000);
checkItem(0);
checkItem(1);
checkItem(numElements - 1);
// count how many items are still in the Map
let cnt = 0;
for (const [index, item] of cache) {
if (item.deref()) {
++cnt;
console.log(`Index item ${index} still in cache`);
}
}
console.log(`There are ${cnt} items that haven't be GCed in the map`);
logUsage();
}
run();
而且,我得到的输出是这样的:
{
id: 9999,
data: [
9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999,
9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999,
9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999,
9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999,
9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999,
9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999,
9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999,
9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999,
9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999,
9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999,
... 9900 more items
]
}
heapUsed: 806,706,120
heapUsed: 806,679,456
undefined
undefined
undefined
There are 0 items that haven't be GCed in the map
heapUsed: 3,412,144
输出中的两行undefined 和最后一个 heapUsed 表明包装在weakRef 引用中的对象确实被GC。
因此,经过足够长的时间延迟,解释器无事可做,只有 weakRef 的数据似乎被 GCed。我还不知道为什么您的示例没有显示这一点,除非我的经验表明仅调用 global.gc() 并不一定会执行与实际空闲解释器相同的 GC。所以,我建议你插入一个合法的暂停(就像我在我的例子中所做的那样),看看你是否最终能恢复记忆。
附:我发布了this other question 关于我在处理此答案时发现的 GC 异常。