- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我的问题是这是否是 nodejs 垃圾收集器的错误?或者这是某种预料之中的事情?
在 Windows 上运行 Node v14.15.0。
在为 this question 寻找答案时涉及 WeakRef 对象时,我发现了一个关于垃圾收集的奇怪事情,它似乎是一个可能的错误。分配给在 for
循环中声明的变量的对象即使在 let
变量超出 for
循环范围后也不会被垃圾回收.此处感兴趣的变量名为 element
,这是它所在的循环。它只是循环的最后一次迭代中未被 GC 处理的对象(element
最后指向):
// fill all the arrays and the cache
// and put everything into the holding array too
for (let i = 0; i < numElements; i++) {
let arr = new Array(lenArrays);
arr.fill(i);
let 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));
}
然后,几行代码之后,我们用这个清除数组holding
:
holding.length = 0;
你会认为在这个循环结束后,在 holding
被清除后,那个循环中 element
的所有值都应该符合 GC 的条件。对它们的唯一引用是通过 WeakRef
对象(不会阻止 GC)。
而且,事实上,如果我让 nodejs 有一些空闲时间,除了 for
循环创建的最后一个对象之外的所有对象都确实被 GC。但是,最后一个不是。如果我将 element = null
添加到 for
循环的末尾,那么最后一个确实会被 GC 处理。因此,nodejs 不会以某种方式清除 element
最后指向的变量上的 refcnt,即使 element
现在超出范围。
所以,你可以在这里看到完整的代码(你可以把它放到一个文件中,然后自己在 nodejs 中运行它):
'use strict';
// 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
for (let i = 0; i < numElements; i++) {
let arr = new Array(lenArrays);
arr.fill(i);
let 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));
}
// 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 been 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: 805,544,472
heapUsed: 805,582,072
undefined
undefined
{
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
]
}
Index item 9999 still in cache
There are 1 items that haven't been GCed in the map
heapUsed: 3,490,168
两行 undefined
是预期的。 id:9999 对象的第二个记录输出不是预期的。它也应该是 undefined
。而且,发现 id:9999 对象仍然在缓存中是不期望的。它应该有资格进行 GC。
一种可能的理论是,V8 优化器将 element
从 for
循环中拉出来,以避免在循环中一遍又一遍地创建它,但随后没有它在循环完成后符合 GC 条件 - 本质上是将其提升到更高的范围。
另一种理论是 GC 并不总是 block 范围粒度。
有没有错误?
最佳答案
这不是错误。我同意这里的行为乍一看很奇怪,但作为 MDN documentation说:
It's also important to avoid relying on any specific behaviors not guaranteed by the specification. When, how, and whether garbage collection occurs is down to the implementation of any given JavaScript engine.
虽然就 JavaScript 语言语义而言,element
在循环之后确实(当然)超出了范围,但是没有保证/ promise /规范 >let
- 指向的循环(或其他 block )中的变量有资格在该 block 的末尾立即进行垃圾回收。引擎是免费的,例如。在内部为这个变量分配一个栈槽,它只会在当前函数结束时被清除;和堆栈槽通常被 GC 视为“根”,即它们使它们指向的内容保持事件状态。
如果未能释放无法访问的对象导致无限内存增长,直到发生 OOM 崩溃,这将是一个错误。但这里的情况并非如此:无论您将 numElements
设置为 1、10 还是 10000,它都是 一个 对象,一直保留到函数结束。
旁注:无需 sleep 五秒钟即可运行 GC; Node 的 global.gc()
很好,您还需要返回到事件循环才能看到 WeakRefs 被清除(正如 MDN 文档也指出的那样)。
编辑添加:
在这种特殊情况下,最后一个元素
存在的具体原因是因为未优化 代码/字节码只是为每个局部变量分配一个栈槽。它不会在函数返回之前清空该槽,因此堆栈槽引用的对象将保持事件状态,直到函数返回。这通常(没有 WeakRefs)是不可观察的,并且只是执行速度、启动延迟、内存消耗、CPU/功耗、代码复杂性和/或引擎做出的其他指标之间的众多权衡之一。这些内部细节有意未记录,因为它们随时可能更改,没有人应该依赖它们(正如 MDN 文档指出的那样)。
如果您强制函数 run
在一段时间后进行优化,优化编译器将花时间进行适当的生命范围分析,这通常会导致堆栈槽在执行函数时被重用于不同的事情进步,并且(至少在这种情况下)导致对象实际上会更快地被垃圾收集。
也就是说,虽然我理解你的好奇心,但我想再次强调:内部细节真的无关紧要。 JS 引擎内部究竟发生了什么在很大程度上取决于整体场景,并且当然会根据您运行的引擎和版本而变化。
关于javascript - 为什么在 for 循环中声明的变量的最后一次迭代没有被垃圾收集?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67646525/
我是 C 新手,还没有真正掌握 C 何时决定释放对象以及何时决定保留对象。 heap_t 是指向结构堆的指针。 heap_t create_heap(){ heap_t h_t = (heap
我有一个问题,我不知道如何解决。问题是: char * ary = new Char[]; ifstream fle; fle.open(1.txt, ios_base::binary); fle.s
假设我在 C# 中有字符串:“我看不到你……” 我想删除(替换为空等)这些“’”符号。 我该怎么做? 最佳答案 那个“垃圾”看起来很像有人将 UTF-8 数据解释为 ISO 8859-1 或 Wi
我无法在解析方法中更改蜘蛛设置。但这绝对是一种方式。 例如: class SomeSpider(BaseSpider): name = 'mySpider' allowed_domains
在开始之前,我们先回顾一下堆是个什么玩意,大家可能都知道,我们每天创建的Java对象几乎都存放在堆上面,所以说堆是一个巨大的对象池一点都不过分,在这个对象池里面管理者数据巨大的对象实例。 在对
我想知道为什么 printf() 在提供数组且没有格式化选项时成功打印字符数组,但在使用整数数组时编译器会抛出警告并打印垃圾值。 这是我的代码: #include int main() { c
我正在研究 Scrapy 库并尝试制作一个小爬虫。 这是爬虫的规则: rules = ( Rule(LinkExtractor(restrict_xpaths='//div[@class="w
这个问题在这里已经有了答案: 关闭 10 年前。 Possible Duplicate: Printing a string to a temporary stream object in C++
这个问题在这里已经有了答案: Are WebGL objects garbage collected? (2 个答案) 关闭 3 年前。 在 WebGL 中,纹理的创建和销毁使用: WebGLTex
我继承了以下代码: (为保护无辜者更改了一些名称。) package foo.bar.baz; import javax.swing.JPanel; //Main panel in the GUI c
如果我没记错的话,在某些情况下,Java 中的 lambda 会生成为匿名类实例。例如,在这段代码中,lambda 需要从外部捕获一个变量: final int local = 123456; lis
我正在阅读托管代码中的内存泄漏,想知道是否可以在 C# 不安全代码中创建它? unsafe { while(true) new int; } 我不确定如果它作为不安全代码运行,是否会被 GC
假设我有以下用 HTML 编写的网页(仅正文部分): ... function fn() { // do stu
我想知道是否有简单的命令可以删除在 latex 编译过程中生成的所有不必要的文件,例如.aux、.log 等 最好将它链接到常规的 Latex 构建命令,这样在我点击“编译”后,垃圾文件就会被删除。
Java 在 Java7 中引入了带有字符串的 switch case。我想知道使用这样的开关盒是否会产生垃圾。 例如在我的程序中, String s = getString(); switch(s)
Cevelop将 char junk 作为“未初始化的变量”对象。在这种情况下,解决问题的正确方法是什么? friend std::ostream& operator>(std::istream&
关闭。这个问题需要debugging details .它目前不接受答案。 编辑问题以包含 desired behavior, a specific problem or error, and t
我正在编写一个发送和接收纯文本的小型 boost asio tcp 服务器和客户端。通信或多或少是请求响应。在测试期间,我想我只是向服务器发送垃圾数据,向它发送 100.000 个请求。 客户端发
我正在使用 SAX 来读取/解析 XML 文档,并且它工作正常,除了这个特定的站点,在该站点中 eclipse 告诉我“文档元素之后的垃圾”并且我没有返回任何数据 http://www.zachblu
这是我的 Scrapy 爬虫代码。我正在尝试从网站中提取元数据值。没有元数据在一个页面上出现多次。 class MySpider(BaseSpider): name = "courses"
我是一名优秀的程序员,十分优秀!