aboutsummaryrefslogtreecommitdiff
path: root/files/ru/web/javascript/memory_management/index.html
blob: 4fa942b9971808c31f523388f984c01db847c142 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
---
title: Управление памятью
slug: Web/JavaScript/Memory_Management
translation_of: Web/JavaScript/Memory_Management
---
<div>{{JsSidebar("Advanced")}}</div>

<h2 id="Введение">Введение</h2>

<p>Низкоуровневые языки программирования (например, C) имеют низкоуровневые примитивы для управления памятью, такие как <code>malloc()</code> и <code>free()</code>. В JavaScript же память выделяется динамически при создании сущностей (т.е., объектов, строк и т.п.) и "автоматически" освобождается, когда они больше не используются. Последний процесс называется <em>сборкой мусора</em> . Слово "автоматически" является источником путаницы и зачастую создаёт у программистов на JavaScript (и других высокоуровневых языках) ложное ощущение, что они могут не заботиться об управлении памятью.</p>

<h2 id="Жизненный_цикл_памяти">Жизненный цикл памяти</h2>

<p>Независимо от языка программирования, жизненный цикл памяти практически всегда один и тот же:</p>

<ol>
 <li>Выделение необходимой памяти.</li>
 <li>Её использование (чтение, запись).</li>
 <li>Освобождение выделенной памяти, когда в ней более нет необходимости.</li>
</ol>

<p>Первые два пункта осуществляются явным образом (т.е., непосредственно программистом) во всех языках программирования. Третий пункт осуществляется явным образом в низкоуровневых языках, но в большинстве высокоуровневых языков, в том числе и в JavaScript, осуществляется автоматически.</p>

<h3 id="Выделение_памяти_в_JavaScript">Выделение памяти в JavaScript</h3>

<h4 id="Выделение_памяти_при_инициализации_значений_переменных">Выделение памяти при инициализации значений переменных</h4>

<p>Чтобы не утруждать программиста заботой о низкоуровневых операциях выделения памяти, интерпретатор JavaScript динамически выделяет необходимую память при объявлении переменных:</p>

<div style="overflow: hidden;">
<pre class="brush: js notranslate">var n = 123; // выделяет память для типа number
var s = "azerty"; // выделяет память для типа string

var o = {
  a: 1,
  b: null
}; // выделяет память для типа object и всех его внутренних переменных

var a = [1, null, "abra"]; // (like object) выделяет память для array и его внутренних значений

function f(a){
  return a + 2;
} // выделяет память для function (которая представляет собой вызываемый объект)

// функциональные выражения также выделяют память под object
someElement.addEventListener('click', function(){
  someElement.style.backgroundColor = 'blue';
}, false);
</pre>
</div>

<h4 id="Выделение_памяти_при_вызовах_функций">Выделение памяти при вызовах функций</h4>

<p>Вызовы некоторых функций также ведут к выделению памяти под объект:</p>

<pre class="brush: js notranslate">var d = new Date();
var e = document.createElement('div'); // выделяет память под DOM-элемент
</pre>

<p>Некоторые методы выделяют память для новых значений или объектов:</p>

<pre class="brush: js notranslate">var s = "azerty";
var s2 = s.substr(0, 3); // s2 это новый объект типа string
// Т.к. строки - это постоянные значения, интерпретатор может решить, что память выделять не нужно, но нужно лишь сохранить диапазон [0, 3].

var a = ["ouais ouais", "nan nan"];
var a2 = ["generation", "nan nan"];
var a3 = a.concat(a2); // новый массив с 4 элементами в результате конкатенации элементов 'a' и 'a2'
</pre>

<h3 id="Использование_значений">Использование значений</h3>

<p>"Использование значений", как правило, означает -  чтение и запись значений из/в выделенной для них области памяти. Это происходит при чтении или записи значения какой-либо переменной, или свойства объекта или даже при передаче аргумента функции.</p>

<h3 id="Освобождение_памяти_когда_она_более_не_нужна">Освобождение памяти, когда она более не нужна</h3>

<p>Именно на этом этапе появляется большинство проблем из области "управления памятью". Наиболее сложной задачей в данном случае является чёткое определение того момента, когда "выделенная память более не нужна". Зачастую программист сам должен определить, что в данном месте программы данная часть памяти более уже не нужна и освободить её.</p>

<p>Интерпретаторы языков высокого уровня снабжаются встроенным программным обеспечением под названием "сборщик мусора", задачей которого является следить за выделением и использованием памяти и при необходимости автоматически освобождать более не нужные участки памяти. Это происходит весьма приблизительно, так как основная проблема точного определения того момента, когда какая-либо часть памяти более не нужна -  <a class="external" href="http://en.wikipedia.org/wiki/Decidability_%28logic%29">неразрешима</a> (т.е., данная проблема не поддаётся однозначному алгоритмическому решению).</p>

<h2 id="Сборка_мусора">Сборка мусора</h2>

<p>Как уже упоминалось выше, проблема точного определения, когда какая-либо часть памяти "более не нужна" - однозначно неразрешима. В результате сборщики мусора решают поставленную задачу лишь частично. В этом разделе мы объясним основополагающие моменты, необходимые для понимания принципа действия основных алгоритмов сборки мусора и их ограничений.</p>

<h3 id="Ссылки">Ссылки</h3>

<p>Большая часть алгоритмов сборки мусора основана на понятии <em>ссылки</em>. В контексте управления памятью объект считается ссылающимся на другой объект, если у первого есть доступ ко второму (неважно - явный или неявный). К примеру, каждый объект JavaScript имеет ссылку на свой <a href="/en/JavaScript/Guide/Inheritance_and_the_prototype_chain" title="en/JavaScript/Guide/Inheritance_and_the_prototype_chain">прототип</a> (неявная ссылка) и ссылки на значения своих полей (явные ссылки).</p>

<p>В данном контексте понятие "объект" понимается несколько шире, нежели для типичных JavaScript-объектов и дополнительно включает в себя понятие областей видимости функций (или глобальной лексической области)</p>

<h3 id="Сборка_мусора_на_основе_подсчёта_ссылок">Сборка мусора на основе подсчёта ссылок</h3>

<p>Это наиболее примитивный алгоритм сборки мусора, сужающий понятие "объект более не нужен" до "для данного объекта более нет ни одного объекта, ссылающегося на него". Объект считается подлежащим уничтожению сборщиком мусора, если количество ссылок на него равно нулю.</p>

<h4 id="Пример">Пример</h4>

<pre class="brush: js notranslate">var o = {
  a: {
    b:2
  }
}; // создано 2 объекта. Один ссылается на другой как на одно из своих полей.
// Второй имеет виртуальную ссылку, поскольку присвоен в качестве значения переменной 'o'.
// Очевидно, что ни один из них не подлежит сборке мусора.


var o2 = o; // переменная 'o2' - вторая ссылка на объект
o = 1; // теперь объект, имевший изначально ссылку на себя из 'o' имеет уникальную ссылку через переменную 'o2'

var oa = o2.a; // ссылка на поле 'a' объекта.
// Теперь на объект 2 ссылки: одна на его поле и вторая - переменная 'oa'

o2 = "yo"; // Объект, на который изначально ссылалась переменная 'o', теперь имеет ноль ссылок на неё.
// Может быть уничтожен при сборке мусора.
// Однако, на его поле 'a' всё ещё ссылается переменная 'oa', так что удалять его ещё нельзя

oa = null; // оригинальное значение поля объекта 'a' в переменной o имеет ноль ссылок на себя.
// можно уничтожить при сборке мусора.
</pre>

<h4 id="Ограничение_циклические_ссылки">Ограничение : циклические ссылки</h4>

<p>Основное ограничение данного наивного алгоритма заключается в том, что если два объекта ссылаются друг на друга (создавая таким образом циклическую ссылку), они не могут быть уничтожены сборщиком мусора, даже если "более не нужны".</p>

<pre class="brush: js notranslate">function f(){
  var o = {};
  var o2 = {};
  o.a = o2; // o ссылается на o2
  o2.a = o; // o2 ссылается на o

  return "azerty";
}

f();
</pre>

<p>Создаётся два ссылающихся друг на друга объекта, что порождает циклическую ссылку. Они не будут удалены из области видимости функции после завершения работы этой функции, таким образом, сборщик мусора не сможет их удалить, несмотря на их очевидную ненужность. Так как сборщик мусора считает, что, раз на каждый из объектов существует как минимум одна ссылка, то уничтожать их нельзя.</p>

<h4 id="Пример_из_реальной_жизни">Пример из реальной жизни</h4>

<p>Браузеры Internet Explorer версий 6, 7 имеют сборщик мусора для DOM-объектов, работающий по принципу подсчёта ссылок. Поэтому данные браузеры можно легко принудить к порождению систематических утечек памяти (memory leaks) следующим образом:</p>

<pre class="brush: js notranslate">var div;
window.onload = function(){
  div = document.getElementById("myDivElement");
  div.circularReference = div; // DOM-элемент "myDivElement" получает ссылку на себя
  div.lotsOfData = new Array(10000).join("*");
};
</pre>

<p>DOM-элемент "myDivElement" имеет циклическую ссылку на самого себя в поле "circularReference". Если это свойство не будет явно удалено или установлено в null, сборщик мусора всегда будет определять хотя бы одну ссылку на DOM-элемент, и будет держать DOM-элемент в памяти, даже если DOM-элемент удалят из DOM-дерева. Таким образом, если DOM-элемент содержит много данных (иллюстрируется полем "lotsOfData"), то память, используемая под эти данные, никогда не будет освобождена.</p>

<h3 id="Алгоритм_Mark-and-sweep">Алгоритм "Mark-and-sweep"</h3>

<p>Данный алгоритм сужает понятие "объект более не нужен" до "объект недоступен".</p>

<p>Основывается на понятии о наборе объектов, называемых <em>roots</em> (в JavaScript root'ом является глобальный объект). Сборщик мусора периодически запускается из этих roots, сначала находя все объекты, на которые есть ссылки из roots, затем все объекты, на которые есть ссылки из найденных и так далее. Стартуя из roots, сборщик мусора, таким образом, находит все <em>доступные</em> объекты и уничтожает недоступные.</p>

<p>Данный алгоритм лучше предыдущего, поскольку "ноль ссылок на объект" всегда входит в понятие "объект недоступен". Обратное же - неверно, как мы только что видели выше на примере циклических ссылок.</p>

<p>Начиная с 2012 года, все современные веб-браузеры оснащаются сборщиками мусора, работающими исключительно по принципу mark-and-sweep ("пометь и выброси"). Все усовершенствования в области сборки мусора в интерпретаторах JavaScript (генеалогическая/инкрементальная/конкурентная/параллельная сборка мусора) за последние несколько лет представляют собой усовершенствования данного алгоритма, но не новые алгоритмы сборки мусора, поскольку дальнейшее сужение понятия "объект более не нужен" не представляется возможным.</p>

<h4 id="Теперь_циклические_ссылки_-_не_проблема">Теперь циклические ссылки - не проблема</h4>

<p>В вышеприведённом первом примере после возврата из функции оба объекта не имеют на себя никаких ссылок, доступных из глобального объекта. Соответственно, сборщик мусора пометит их как недоступные и затем удалит.</p>

<p>То же самое касается и второго примера. Как только div и его обработчик станут недоступны из roots, они оба будут уничтожены сборщиком мусора, несмотря на наличие циклических ссылок друг на друга.</p>

<h4 id="Ограничение_некоторые_объекты_нуждаются_в_явном_признаке_недоступности">Ограничение: некоторые объекты нуждаются в явном признаке недоступности</h4>

<p>Хотя этот частный случай и расценивается, как ограничение, но на практике он встречается крайне редко, поэтому, в большинстве случаев, вам не нужно беспокоиться о сборке мусора.</p>

<h2 id="Смотрите_также">Смотрите также</h2>

<ul>
 <li><a class="external" href="http://www.ibm.com/developerworks/web/library/wa-memleak/">статья IBM "Шаблоны утечек памяти в JavaScript" (2007)</a></li>
 <li><a class="external" href="http://msdn.microsoft.com/en-us/magazine/ff728624.aspx">статья Kangax о том, как правильно регистрировать обработчики событий для предотвращения утечек памяти (2010)</a></li>
 <li><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Performance" title="https://wiki.mozilla.org/Performance:Leak_Tools">Performance</a></li>
</ul>