2015-12-27 2 views
2

Я пытаюсь понять, почему следующий код вызывает утечку памятиJavaScript утечка памяти от закрытия лексической среды

var aThing = null; 
 
var outer = function() { 
 

 
    console.log('running'); 
 
    var something = aThing; 
 

 
    var closure1 = function() { 
 
     if (something) { 
 
      console.log('something'); 
 
     } 
 
    }; 
 

 
    aThing = { 
 
     str: new Array(1000000).join('8'), 
 
     someMethod: function() {} 
 
    }; 
 
}; 
 
setInterval(outer, 1000);

Вот это показывает памяти график увеличения от Google Chrome:

Memory Leak

но этот код, который является очень незначительным вариантом ы не вызывают такую ​​же утечку памяти:

var aThing = null; 
 
var outer = function() { 
 

 
    console.log('running'); 
 
    var something = aThing; 
 
    var closure1 = function() { 
 
     if (something) { 
 
      console.log('something'); 
 
     } 
 
    } 
 

 
    aThing = { 
 
     str: new Array(1000000).join('8') 
 
    }; 
 

 
    function someMethod() {}; 
 
}; 
 
setInterval(outer, 1000);

Вот эквивалент график, показывающий, что ГХ очистки ОК.

No leak

Я понимаю, что в первой версии есть утечка памяти, так как переменная «что-то» не порядок. Почему он включен в второй пример, но не первый?

+0

1. Избегайте менять более одного предмета. Несмотря на то, что это почти бесполезно, не меняйте порядок первых двух утверждений в 'outer' **, а также ** делайте что-то еще. Выяснение этих факторов требует создания ** одного ** изменения во время и устранения нерелевантности. 2. При обращении за помощью, пожалуйста, уделите время, чтобы сделать код подданным читаемым и последовательным. Я исправил №2 для вас. –

+1

OP, интересно @ T.J.Crowder [пишет о сборке мусора здесь] (http://stackoverflow.com/questions/4324133/how-does-garbage-collection-work-in-javascript), который может немного помочь вам. – Andy

ответ

1

Первичный ответ, что в вашем втором блоке кода, ни один из затворов (closure1 или someMethod) выживает возвращение outer (ничего вне outer относится к ним), и поэтому там не осталось ничего, что относится к контексту, где они были созданы, и этот контекст можно очистить. Однако во втором блоке кода someMethod выживает при возврате, как часть объекта, который вы назначаете aThing, и поэтому контекст не может быть GC'd.

Давайте проследим, что происходит с вашим первым блоком:

После первого исполнения outer, мы (не обращая внимания на кучу деталей):

 
      +-------------+ 
aThing----->| (object #1) |  
      +-------------+  
      | str: ... |  +--------------------+ 
      | someMethod |---->| (context #1)  | 
      +-------------+  +--------------------+ 
           | something: null | 
           | closure1: function | 
           +--------------------+ 

после выполнения на второго:

 
      +-------------+ 
aThing----->| (object #2) |  
      +-------------+  
      | str: ... |  +--------------------+  
      | someMethod |---->| (context #2)  |  
      +-------------+  +--------------------+  +-------------+       
           | something   |---->| (object #1) |       
           | closure1: function |  +-------------+       
           +--------------------+  | str: ... |  +--------------------+ 
                  | someMethod |---->| (context #1)  | 
                  +-------------+  +--------------------+ 
                       | something: null | 
                       | closure1: function | 
                       +--------------------+ 

после третий исполнитель на:

 
      +-------------+ 
aThing----->| (object #3) |  
      +-------------+  
      | str: ... |  +--------------------+  
      | someMethod |---->| (context #3)  |  
      +-------------+  +--------------------+  +-------------+                   
           | something   |---->| (object #2) |                   
           | closure1: function |  +-------------+                   
           +--------------------+  | str: ... |  +--------------------+            
                  | someMethod |---->| (context #2)  |            
                  +-------------+  +--------------------+  +-------------+       
                       | something   |---->| (object #1) |       
                       | closure1: function |  +-------------+       
                       +--------------------+  | str: ... |  +--------------------+ 
                              | someMethod |---->| (context #1)  | 
                              +-------------+  +--------------------+ 
                                   | something: null | 
                                   | closure1: function | 
                                   +--------------------+ 

Вы можете видеть, куда это направляется.

Поскольку второй блок никогда не сохраняет ссылку на closure1 или someMethod, ни один из них не сохраняет контекст в памяти.

Я немного удивлен тем, что V8 (двигатель JavaScript в Chrome) не оптимизирует эту утечку далеко, поскольку сохраняется только someMethod и someMethod фактически не используют something или closure1 (или eval или new Function или debugger), так хотя теоретически он имеет ссылки на них через контекст, статический анализ показал бы, что они фактически не могут использоваться и поэтому могут быть отброшены. Но оптимизация закрытия на самом деле легко беспокоить, я думаю, что что-то в ней беспокоит.

+0

Большое спасибо @ T.J. Кроудер, диаграммы особенно полезны в понимании этого. FYI причина, что 'someMethod' сохраняется, состоит в том, что' closeure1' относится к originalThing. Как только ограничение _any_ ссылается на локальную переменную, эта переменная будет в контексте всех закрытий. Если вы удалите 'clos1', он не будет течь, даже если' clos1' никогда не используется! – user3391835

+0

@ user3391835: Это теория, но опять же, потому что ничто фактически не обращается к 'clos1', оно может быть оптимизировано. Теория состоит в том, что локали хранятся в объекте, а замыкания имеют ссылку на этот объект. Реальность в современных двигателях, конечно, намного сложнее. :-) –

Смежные вопросы