-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
对于内存释放的例子,感觉不是很好理解 #7
Comments
这个其实你从这方面理解:在V8当前版本中,闭包对象是当前作用域中的所有内部函数作用域共享的,并且这个当前作用域的闭包对象中除了包含一条指向上一层作用域闭包对象的引用外,其余的存储的变量引用一定是当前作用域中的所有内部函数作用域中使用到的变量。 这样就可以解释泄漏的原因了:在 这样就造成了 其实这里要解决也比较简单:一种就是你的第二种修改方式,手动在每次调用完成后把 |
@hyj1991 学到了,没留意到theThing.someMethod函数 根据你上面说的第二种取消必包引用,补充如下:(不知理解对不) var theThing = '';
var replaceThing = function () {
let 泄漏变量 = !!theThing;
let unused = function () {
if (泄漏变量)
console.log("hi")
};
theThing = {
longStr: new Array(1000000).join('*'),
someMethod: function () {
console.log('a')
}
};
global.gc();
console.log(process.memoryUsage().heapUsed);
};
setInterval(replaceThing, 100); 但是这样theThing.someMethod方法依然占用两个变量(但不会内存上升),是不是应该每次都要在最后调用 泄漏变量 = null;
unused = null; 1、这样看来是不是应该减少匿名函数使用,或有什么更好的方法? 然后不断的学习,又发现一个解决方案:(利用let的块级作用域) var theThing = '';
var replaceThing = function () {
{
let 泄漏变量 = theThing; // 如果是var则依然会内存上升
let unused = function () {
if (泄漏变量)
console.log("hi")
};
}
// 不断修改引用
theThing = {
longStr: new Array(1000000).join('*'),
someMethod: function () {
console.log('a')
}
};
global.gc();
console.log(process.memoryUsage().heapUsed);
};
setInterval(replaceThing, 100); |
Hi, 还可以: let unused = function (内部变量) {
if (内部变量)
console.log("hi")
}; 这样 immutable 一下貌似也可以了 |
@aogg ,你的两种修改方式其实和提问的本意有一些区别了。 let unused = function (泄漏变量) {
if (泄漏变量)
console.log("hi")
}; 这样 至于你写的第二种处理方式,是因为let的块级作用域,导致整个 {
let 泄漏变量 = theThing; // 如果是var则依然会内存上升
let unused = function () {
if (泄漏变量)
console.log("hi")
};
} 这个一整个部分和 |
@hyj1991 对,应该通过传参来减少函数对外部变量的依赖。好像js对这种处理方式用的会比较多,再补充个完整的: var theThing = '';
var replaceThing = function () {
let 泄漏变量 = theThing;
let unusedSync = function (泄漏变量) { // 同步执行
if (泄漏变量)
console.log("hii")
};
unusedSync(泄漏变量);
let unused = 泄漏变量 => function () { // 异步执行,就多一层
if (泄漏变量)
console.log("hi")
};
setTimeout(unused(泄漏变量), 0);
theThing = {
longStr: new Array(1000000).join('*'),
someMethod: function () {
console.log('a')
}
};
global.gc();
console.log(process.memoryUsage().heapUsed);
};
setInterval(replaceThing, 100); |
@hyj1991 hyj1991 也许可以这样? 但是为什么这样可以?
|
@gjc9620 你这个问题我觉得可以分两个方面讨论下:
有误之处还请指正 |
@hyj1991
|
话说回来这个unused并没有使用 不会被GC清除吗?这个问题似乎很复杂和GC如何执行有关 |
@gjc9620 第二个问题:你可以仔细看看我的第一个回答:实际上同一个作用域生成的闭包对象是被该作用域中所有下一级作用域共同持有的,正是因为 |
@gjc9620 |
@hyj1991 其实 隐式地持有变量 和 显式地持有变量 会导致什么或者说有什么区别? 这个内存泄漏问题是因为同时存在两个函数与一个不断变化引用的新对象变量,其中两个函数一个显式持有一个隐式持有(满足这三个条件就会导致内存泄漏)。 |
@hyj1991 |
隐式还是显式只是一个说法罢了,实际上就是js闭包对象除了保持一条指向上一级作用域闭包对象的引用外,还会包含所有下面作一级域使用到的本作用域定义的变量,看这段吧:
|
@hyj1991 谢谢 请问文章出处? |
Feel free to reopen. |
@Lellansin 和频率有什么关系?难道内存会自己慢慢增长? |
如果 v8 中的实现细节真的如 @hyj1991 大佬说的那样,那我觉得把这个当做是 v8 的缺陷来说比较好。 首先闭包的基本概念是:
function f() {
let a = 1
return function () { console.log(a) }
} 上面代码中
function f() {
let a = 1
return [
function () { a++ },
function () { console.log(a) }
]
}
const [f1, f2] = f();
f1();
f2(); 上面代码中 说回 JS 中引入闭包的目的,就是为了让作为一等公民的函数、也能继续沿用词法作用域。在此之后衍生出来的比如模拟私有变量之类,都只能算作是用途。
这是因为闭包被创建了之后,除了与之关联的函数,再没有路径可以访问到它。所以只有函数对象被标记为垃圾对象,与之关联的闭包才会变为垃圾对象。当然并不是函数对象成了垃圾之后,闭包中的内容就都成了垃圾,考虑被捕获的变量具有共享的特性,只有当共享它的所有闭包都成了垃圾时,那个被捕获的共享对象才会成为垃圾。 回到例子中的代码: var theThing = null
var replaceThing = function () {
var originalThing = theThing
var unused = function () { // closure1
if (originalThing)
console.log("hi")
}
theThing = {
longStr: new Array(1000000).join('*'),
someMethod: function () { // closure2
console.log('a')
}
};
};
setInterval(replaceThing, 1000) 按照我上面的描述,这里应该有两个闭包,分别是 如果是分开的两个闭包,那么很明显并不会导致内存泄露,因为 closure1 会因为 而 v8 的实现中,将 从技术来说,即使合二为一了,也应该能够做到 为了再验证一下其他语言或者引擎对闭包的实现,我分别尝试了 CLua 和 quickjs 同样闭包的概念在 lua 中也有,CLua 的实现不会导致内存泄露: function createArray(size)
local a = {}
for i=1,size do
a[i] = 0
end
return a
end
local theThing = nil
local replaceThing = function ()
local originalThing = theThing
local unused = function ()
if originalThing then
print('hi');
end
end
theThing = {
longStuff = createArray(1 << 20);
someMethod = function ()
print('a')
end
}
end
while(true) do
replaceThing()
end 在最新的 quickjs 中执行等价代码,也不会有内存泄露: var theThing = null;
var replaceThing = function () {
var a = theThing;
var unused = function () {
if (a)
console.log("hi");
};
theThing = {
longStr: new Array(1 << 20).join('*'),
someMethod: function () {
console.log('a');
}
};
};
while(true) {
replaceThing();
} 有错误的话还请指正。 |
我用Node8版本测试有内存泄露问题,但是Node12没有。可能确实是当时版本的一个缺陷,有人能找到相关更新修复说明吗。 |
这是来自QQ邮箱的假期自动回复邮件。
您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。
|
对于内存释放的例子,感觉不是很好理解(每次都是一个例子)。不知道下面理解是否正确?
运行时添加 -expose-gc的node命令行参数。
https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#内存释放
正确修改1
正确修改2
The text was updated successfully, but these errors were encountered: