問題描述
和ECMAScriptv5一樣,每次控件輸入代碼時,引擎都會創建一個LexicalEnvironment(LE)和一個VariableEnvironment(VE),用于功能代碼,這兩個對象是完全相同的引用,這是調用 NewDeclarativeEnvironment(ECMAScriptv5 10.4.3),函數代碼中聲明的所有變量都存儲在VariableEnvironment(ECMAScript v5 10.5),這是閉包的基本概念.
as ECMAScriptv5, each time when control enters a code, the enginge creates a LexicalEnvironment(LE) and a VariableEnvironment(VE), for function code, these 2 objects are exactly the same reference which is the result of calling NewDeclarativeEnvironment(ECMAScript v5 10.4.3), and all variables declared in function code are stored in the environment record componentof VariableEnvironment(ECMAScript v5 10.5), and this is the basic concept for closure.
讓我感到困惑的是 Garbage Collect 如何使用這種閉包方法,假設我的代碼如下:
What confused me is how Garbage Collect works with this closure approach, suppose I have code like:
function f1() {
var o = LargeObject.fromSize('10MB');
return function() {
// here never uses o
return 'Hello world';
}
}
var f2 = f1();
在 var f2 = f1()
行之后,我們的對象圖將是:
after the line var f2 = f1()
, our object graph would be:
global -> f2 -> f2's VariableEnvironment -> f1's VariableEnvironment -> o
據我所知,如果javascript引擎使用引用計數方法進行垃圾回收,那么對象o
至少有1個引用strong> 并且永遠不會被 GC.顯然這會導致內存浪費,因為 o
永遠不會被使用,而是始終存儲在內存中.
so as from my little knowledge, if the javascript engine uses a reference counting method for garbage collection, the object o
has at lease 1 refenrence and would never be GCed. Appearently this would result a waste of memory since o
would never be used but is always stored in memory.
可能有人說引擎知道f2的VariableEnvironment不使用f1的VariableEnvironment,所以整個f1的VariableEnvironment都會被GC,所以還有另一個代碼片段可能會導致更復雜的情況:
Someone may said the engine knows that f2's VariableEnvironment doesn't use f1's VariableEnvironment, so the entire f1's VariableEnvironment would be GCed, so there is another code snippet which may lead to more complex situation:
function f1() {
var o1 = LargeObject.fromSize('10MB');
var o2 = LargeObject.fromSize('10MB');
return function() {
alert(o1);
}
}
var f2 = f1();
在這種情況下,f2
使用o1
對象,該對象存儲在f1的VariableEnvironment中,所以f2的VariableEnvironment必須保留對f1的VariableEnvironment的引用,導致o2
也不能被GC,進一步造成內存浪費.
in this case, f2
uses the o1
object which stores in f1's VariableEnvironment, so f2's VariableEnvironment must keep a reference to f1's VariableEnvironment, which result that o2
cannot be GCed as well, which further result in a waste of memory.
所以我想問一下,現代 javascript 引擎(JScript.dll/V8/SpiderMonkey ...)如何處理這種情況,是否有標準的指定規則或者它是否基于實現,以及 javascript 引擎處理這種情況的確切步驟是什么執行垃圾回收時的對象圖.
so I would ask, how modern javascript engine (JScript.dll / V8 / SpiderMonkey ...) handles such situation, is there a standard specified rule or is it implementation based, and what is the exact step javascript engine handles such object graph when executing Garbage Collection.
謝謝.
推薦答案
tl;dr 回答: "只有從內部 fns 引用的變量才會在 V8 中進行堆分配.如果使用 eval,則假定所有變量都被引用.".在您的第二個示例中, o2
可以在堆棧上分配并在 f1
退出后被丟棄.
tl;dr answer: "Only variables referenced from inner fns are heap allocated in V8. If you use eval then all vars assumed referenced.". In your second example, o2
can be allocated on the stack and is thrown away after f1
exits.
我認為他們無法應付.至少我們知道有些引擎不能,因為眾所周知這是導致許多內存泄漏的原因,例如:
I don't think they can handle it. At least we know that some engines cannot, as this is known to be the cause of many memory leaks, as for example:
function outer(node) {
node.onclick = function inner() {
// some code not referencing "node"
};
}
inner
在 node
上閉合,形成一個循環引用 inner ->外部變量上下文->節點->inner
,在例如 IE6 中永遠不會被釋放,即使 DOM 節點已從文檔中刪除.不過有些瀏覽器處理得很好:循環引用本身不是問題,問題在于 IE6 中的 GC 實現.但現在我離題了.
where inner
closes over node
, forming a circular reference inner -> outer's VariableContext -> node -> inner
, which will never be freed in for instance IE6, even if the DOM node is removed from the document. Some browsers handle this just fine though: circular references themselves are not a problem, it's the GC implementation in IE6 that is the problem. But now I digress from the subject.
打破循環引用的一種常用方法是在 outer
的末尾清除所有不必要的變量.即,設置 node = null
.那么問題是現代 javascript 引擎是否可以為您執行此操作,它們能否以某種方式推斷出 inner
中未使用變量?
A common way to break the circular reference is to null out all unnecessary variables at the end of outer
. I.e., set node = null
. The question is then whether modern javascript engines can do this for you, can they somehow infer that a variable is not used within inner
?
我認為答案是否定的,但我可以被證明是錯誤的.原因是下面的代碼執行得很好:
I think the answer is no, but I can be proven wrong. The reason is that the following code executes just fine:
function get_inner_function() {
var x = "very big object";
var y = "another big object";
return function inner(varName) {
alert(eval(varName));
};
}
func = get_inner_function();
func("x");
func("y");
使用這個 jsfiddle 示例 親自查看.inner
內沒有對 x
或 y
的引用,但仍可使用 eval
訪問它們.(令人驚訝的是,如果您將 eval
別名為其他名稱,例如 myeval
,然后調用 myeval
,您不會獲得新的執行上下文 - 這是即使在規范中,請參閱 ECMA-262 中的第 10.4.2 和 15.1.2.1.1 節.)
See for yourself using this jsfiddle example. There are no references to either x
or y
inside inner
, but they are still accessible using eval
. (Amazingly, if you alias eval
to something else, say myeval
, and call myeval
, you DO NOT get a new execution context - this is even in the specification, see sections 10.4.2 and 15.1.2.1.1 in ECMA-262.)
根據您的評論,似乎一些現代引擎實際上做了一些聰明的把戲,所以我試著再挖掘一點.我遇到了這個 論壇帖子 討論這個問題,特別是一個鏈接一條關于如何在 V8 中分配變量的推文.它還專門涉及 eval
問題.似乎它必須解析所有內部函數中的代碼.并查看引用了哪些變量,或者是否使用了 eval
,然后確定每個變量應該分配在堆上還是堆棧上.挺整潔的.這是 另一個博客,其中包含許多有關 ECMAScript 實現的詳細信息.
As per your comment, it appears that some modern engines actually do some smart tricks, so I tried to dig a little more. I came across this forum thread discussing the issue, and in particular, a link to a tweet about how variables are allocated in V8. It also specifically touches on the eval
problem. It seems that it has to parse the code in all inner functions. and see what variables are referenced, or if eval
is used, and then determine whether each variable should be allocated on the heap or on the stack. Pretty neat. Here is another blog that contains a lot of details on the ECMAScript implementation.
這意味著即使內部函數從不逃避"調用,它仍然可以強制在堆上分配變量.例如:
This has the implication that even if an inner function never "escapes" the call, it can still force variables to be allocated on the heap. E.g.:
function init(node) {
var someLargeVariable = "...";
function drawSomeWidget(x, y) {
library.draw(x, y, someLargeVariable);
}
drawSomeWidget(1, 1);
drawSomeWidget(101, 1);
return function () {
alert("hi!");
};
}
現在,由于 init
已完成調用,someLargeVariable
不再被引用并且應該可以刪除,但我懷疑它不是,除非內部函數drawSomeWidget
已被優化掉(內聯?).如果是這樣,當使用自執行函數來模仿具有私有/公共方法的類時,這可能會經常發生.
Now, as init
has finished its call, someLargeVariable
is no longer referenced and should be eligible for deletion, but I suspect that it is not, unless the inner function drawSomeWidget
has been optimized away (inlined?). If so, this could probably occur pretty frequently when using self-executing functions to mimick classes with private / public methods.
回答下面的 Raynos 評論.我在調試器中嘗試了上述場景(稍作修改),結果和我預測的一樣,至少在 Chrome 中:
Answer to Raynos comment below. I tried the above scenario (slightly modified) in the debugger, and the results are as I predict, at least in Chrome:
執行內部函數時,someLargeVariable 仍在作用域內.
When the inner function is being executed, someLargeVariable is still in scope.
如果我在內部 drawSomeWidget
方法中注釋掉對 someLargeVariable
的引用,那么你會得到不同的結果:
If I comment out the reference to someLargeVariable
in the inner drawSomeWidget
method, then you get a different result:
現在 someLargeVariable
不在范圍內,因為它可以在堆棧上分配.
Now someLargeVariable
is not in scope, because it could be allocated on the stack.
這篇關于關于閉包、LexicalEnvironment 和 GC的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!