О закрытии, LexicalEnvironment и GC

как ECMAScriptv5, каждый раз, когда элемент управления вводит код, enginge создает LexicalEnvironment (LE) и VariableEnvironment (VE) для кода функции, эти 2 объекта являются точно такой же ссылкой, что является результатом вызова NewDeclarativeEnvironment (ECMAScript v5 10.4.3), и все объявленные переменные в функциональном коде хранятся в файле среды компонента VariableEnvironment (ECMAScript v5 10.5), и это базовое понятие для закрытия.

Что меня смутило, так как Garbage Collect работает с этим методом закрытия, предположим, что у меня есть код вроде:

function f1() {
    var o = LargeObject.fromSize('10MB');
    return function() {
        // here never uses o
        return 'Hello world';
    }
}
var f2 = f1();

после строки var f2 = f1() наш графический объект будет:

global -> f2 -> f2 VariableEnvironment -> f1 VariableEnvironment -> o

так как из моего небольшого знания, если механизм javascript использует метод подсчета ссылок для сбора мусора, объект o имеет в аренду 1 refenrence и никогда не будет быть GCed. По-видимому, это приведет к пустой трате памяти, поскольку o никогда не будет использоваться, но всегда будет храниться в памяти.

Кто-то может сказать, что движок знает, что f2 VariableEnvironment не использует f1 VariableEnvironment, поэтому вся f1 VariableEnvironment будет GCed, поэтому есть еще один фрагмент кода, который может привести к более сложной ситуации:

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 также не может быть GCed, что приводит к пустой трате памяти.

поэтому я хотел бы спросить, как современный javascript engine (JScript.dll/V8/SpiderMonkey...) обрабатывает такую ​​ситуацию, есть ли стандартное заданное правило или оно основано на реализации, и каков точный механизм JavaScript javascript графа объектов при выполнении коллекции мусора.

Спасибо.

Ответ 1

tl; dr answer: "Только переменные, на которые ссылаются внутренние fns, представляют собой кучу, выделенную в V8. Если вы используете eval, тогда все vars предположительно ссылаются" .. В вашем втором примере o2 можно выделить в стеке и выбросить после завершения f1.


Я не думаю, что они справятся с этим. По крайней мере, мы знаем, что некоторые двигатели не могут, поскольку это, как известно, является причиной многих утечек памяти, например:

function outer(node) {
    node.onclick = function inner() { 
        // some code not referencing "node"
    };
}

где inner закрывается над node, образуя круговую ссылку inner -> outer VariableContext -> node -> inner, которая никогда не будет освобождена, например, для IE6, даже если DOM node будет удален из документа. Некоторые браузеры прекрасно справляются с этим, хотя: круговые ссылки сами по себе не являются проблемой, проблема с GC в IE6 является проблемой. Но теперь я отвлекаюсь от темы.

Общим способом разбить круговую ссылку является удаление всех ненужных переменных в конце outer. I.e, set node = null. Вопрос в том, могут ли современные javascript-модули сделать это для вас, могут ли они каким-то образом сделать вывод о том, что переменная не используется в inner?

Я думаю, что ответ отрицательный, но я могу оказаться ошибочным. Причина в том, что следующий код выполняется просто отлично:

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, вы НЕ получите новый контекст исполнения - это даже в спецификации, см. Разделы 10.4.2 и 15.1.2.1. 1 в ECMA-262.)


Изменить: согласно вашему комментарию, кажется, что некоторые современные двигатели действительно делают некоторые умные трюки, поэтому я попытался копать немного больше. Я столкнулся с этим форумом, обсуждая проблему и, в частности, ссылку на твиттер о том, как переменные выделяются в V8. В нем также конкретно затрагивается проблема eval. Кажется, что он должен разбирать код во всех внутренних функциях. и посмотрите, какие переменные ссылаются, или если используется eval, а затем определить, должна ли каждая переменная быть выделена в куче или в стеке. Довольно аккуратно. Вот еще один блог, в котором содержится подробная информация о реализации ECMAScript.

Это подразумевает, что даже если внутренняя функция никогда не "ускользает" от вызова, она все равно может заставить переменные выделяться в куче. Например:.

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 не была оптимизирована (inlined?). Если это так, это может произойти довольно часто при использовании самоисполняющихся функций для сопоставления классов с частными/общедоступными методами.


Ответ на комментарий Raynos ниже. Я пробовал описанный выше сценарий (слегка измененный) в отладчике, и результаты, как я предсказываю, по крайней мере в Chrome:

Screenshot of Chrome debugger Когда внутренняя функция выполняется, someLargeVariable все еще находится в области видимости.

Если я прокомментирую ссылку на someLargeVariable во внутреннем методе drawSomeWidget, вы получите другой результат:

Screenshot of Chrome debugger 2 Теперь someLargeVariable не входит в область видимости, потому что он может быть выделен в стеке.

Ответ 2

Стандартных спецификаций для GC нет, каждый движок имеет собственную реализацию. Я знаю небольшую концепцию v8, у нее очень впечатляющий сборщик мусора (стоп-мир, поколение, точность). Как показано выше в примере 2, двигатель v8 имеет следующий шаг:

  • create f1 Объект VariableEnvironment с именем f1.
  • после создания этого объекта V8 создает начальный скрытый класс f1, называемый H1.
  • указывает, что точка f1 равна f2 в корневом уровне.
  • создайте еще один скрытый класс H2, основанный на H1, затем добавьте информацию в H2, которая описывает объект как имеющее одно свойство o1, сохранит его со смещением 0 в объекте f1.
  • обновляет f1 указывает на H2, указанный f1 должен использовать H2 вместо H1.
  • создает еще один скрытый класс H3 на основе H2 и добавляет свойство o2, сохраняет его со смещением 1 в объекте f1.
  • обновления f1 указывают на H3.
  • создать анонимный объект VariableEnvironment с именем a1.
  • создать начальный скрытый класс a1, называемый A1.
  • указывает, что родитель a1 равен f1.

В литературе функции синтаксического анализа создается функция FunctionBody. Только синтаксический анализ FunctionBody при вызове функции. Следующий код указывает на то, что он не выдает ошибку во время парсера

function p(){
  return function(){alert(a)}
}
p();

Итак, при GC-времени H1, H2 будет сметена, потому что нет контрольной точки. На мой взгляд, если код лениво скомпилирован, ни один способ указать переменную o1, объявленную в a1, не является ссылкой на f1, он использует JIT.

Ответ 3

если движок javascript использует метод подсчета ссылок

В большинстве движков javascript используется какой-либо вариант уплотнения и развертки сборщика мусора, а не простой подсчет GC, поэтому эталонные циклы не вызывают проблемы.

Они также склонны делать некоторые трюки, так что циклы, которые связаны с узлами DOM (которые являются ссылками, подсчитанными браузером вне кучи JavaScript), не вводят безнадежных циклов. Сборщик циклов XPCOM делает это для Firefox.

Сборщик циклов тратит большую часть своего времени на накопление (и забывание) указателей на объекты XPCOM, которые могут быть задействованы в циклах мусора. Это незанятая стадия операции коллектора, в которой специальные варианты nsAutoRefCnt регистрируются и очень быстро регистрируются у сборщика, когда они проходят через "подозрительное" событие refcount (от N + 1 до N, для ненулевого N).

Периодически сборщик просыпается и проверяет подозрительные указатели, которые сидели в его буфере некоторое время. Это этап сканирования операции коллектора. На этом этапе коллекционер неоднократно задает каждому кандидату один класс вспомогательного цикла цикла, и если этот помощник существует, сборщик просит помощника описать кандидатов (принадлежащих к кандидату). Таким образом, сборщик создает изображение подграфа собственности, доступного из подозрительных объектов.

Если коллекционер обнаруживает группу объектов, все из которых ссылаются друг на друга, и устанавливает, что подсчеты ссылок объектов учитываются внутренними указателями внутри группы, он считает, что циклический мусор группы, который затем пытается свободно. Это незатухающий этап работы коллектора. На этом этапе коллекционер проходит через найденные объекты мусора, снова консультируясь со своими вспомогательными объектами, прося вспомогательные объекты "отсоединить" каждый объект от его непосредственных детей.

Обратите внимание, что коллекционер также знает, как пройти через кучу JS, и может найти циклы собственности, которые проходят и выходят из него.

Гармония EcmaScript, вероятно, включает эфемероны, а также для обеспечения слабо удерживаемых ссылок.

Вы можете найти "Будущее управления памятью XPCOM" .