aboutsummaryrefslogtreecommitdiff
path: root/files/uk/web/javascript/memory_management/index.html
blob: 0022a811294be80a031a799e0db42ac655848022 (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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
---
title: Керування пам'яттю
slug: Web/JavaScript/Memory_Management
tags:
  - JavaScript
  - Збирання сміття
  - пам'ять
  - продуктивність
translation_of: Web/JavaScript/Memory_Management
---
<div>{{JsSidebar("Advanced")}}</div>

<p>У низькорівневих мовах, наприклад C, існують примітивні функції для ручного керування пам'яттю, такі як <a href="https://pubs.opengroup.org/onlinepubs/009695399/functions/malloc.html">malloc()</a> та <a href="https://en.wikipedia.org/wiki/C_dynamic_memory_allocation#Overview_of_functions">free()</a>. В той же час JavaScript автоматично виділяє пам'ять при створенні об'єктів та звільняє її, коли вони більше не використовуються (<em>збирання сміття</em>). Ця автоматичність є потенційним джерелом плутанини: вона може дати розробникам хибне враження, що їм не потрібно хвилюватись щодо керування пам'яттю.</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>

<pre class="brush: js">var n = 123; // виділяє пам'ять для числа
var s = 'блабла'; // виділяє пам'ять для рядка

var o = {
  a: 1,
  b: null
}; // виділяє пам'ять для об'єкта та значень, що він містить

// (як з об'єктом) виділяє пам'ять для масиву та
// значень, що він містить
var a = [1, null, 'аовл'];

function f(a) {
  return a + 2;
} // виділяє пам'ять для функції (об'єкт, який можна викликати)

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

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

<p>Виклики деяких функцій виділяють пам'ять під об'єкт.</p>

<pre class="brush: js">var d = new Date(); // виділяє пам'ять під об'єкт Date

var e = document.createElement('div'); // видділяє пам'ять під елемент DOM</pre>

<p>Деякі методи виділяють пам'ять для нових значень чи об'єктів:</p>

<pre class="brush: js">var s = 'йцуке';
var s2 = s.substr(0, 3); // s2 - новий рядок
// Оскільки рядки є незмінними,
// JavaScript може вирішити не виділяти пам'ять,
// а лише зберегти діапазон [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>Низькорівневі мови вимагають, щоб розробник вручну визначав, у якій точці програми виділена пам'ять більше не потрібна, та звільняв її.</p>

<p>Деякі високорівневі мови, такі як JavaScript, використовують форму автоматичного керування пам'яттю, відому як <a href="https://uk.wikipedia.org/wiki/%D0%97%D0%B1%D0%B8%D1%80%D0%B0%D0%BD%D0%BD%D1%8F_%D1%81%D0%BC%D1%96%D1%82%D1%82%D1%8F">збирання сміття</a> (ЗС). Збирач сміття відслідковує виділення пам'яті та визначає момент, коли блок виділеної пам'яті більше не потрібний, і повертає його. Цей автоматичний процес є неточним, оскільки загальна проблема визначення, чи потрібна наразі та чи інша ділянка пам'яті, є <a href="https://uk.wikipedia.org/wiki/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D1%96%D1%87%D0%BD%D0%B0_%D1%80%D0%BE%D0%B7%D0%B2%27%D1%8F%D0%B7%D0%BD%D1%96%D1%81%D1%82%D1%8C">нерозв'язною</a>.</p>

<h2 id="Збирання_сміття">Збирання сміття</h2>

<p>Як було зазначено вище, загальна проблема автоматичного визначення того, що якась ділянка пам'яті "більше не потрібна", є нерозв'язною. Як наслідок, збирачі сміття впроваджують обмеження вирішення загальної проблеми. Цей розділ пояснює концепції,  необхідні для розуміння головних алгоритмів збирання сміття та відповідних обмежень.</p>

<h3 id="Посилання">Посилання</h3>

<p>Головна концепція, на яку покладаються алгоритми збирачів сміття - це концепція <em>посилань</em>. У контексті керування пам'яттю об'єкт посилається на інший об'єкт, якщо звертається до нього (явно чи неявно). Наприклад, об'єкт JavaScript має посилання на свій <a href="/uk/docs/Web/JavaScript/Inheritance_and_the_prototype_chain">прототип</a> (неявне посилання) та на значення своїх властивостей (явне посилання).</p>

<p>У цьому контексті поняття "об'єкта" є ширшим, ніж звичайні об'єкти JavaScript, і містить також області видимості функцій (або глобальну область видимості).</p>

<h3 id="Збирання_сміття_через_підрахунок_посилань">Збирання сміття через підрахунок посилань</h3>

<p>Це найпримітивніший алгоритм збирання сміття. Цей алгоритм звужує проблему з визначення того, чи об'єкт досі є потрібним, до визначення того, чи мають інші об'єкти досі посилання на даний об'єкт. Об'єкт називається "сміттям", або підлягає збиранню, якщо на нього вказує нуль посилань.</p>

<h4 id="Приклад">Приклад</h4>

<pre class="brush: js">var x = {
  a: {
    b: 2
  }
};
// Створено 2 об'єкта. Один має посилання у іншому як одна з його властивостей.
// Інший має посилання через присвоєння його змінній 'x'.
// Зрозуміло, що жоден з них не може бути прибраний.


var y = x;      // Змінна 'y' - друга сутність, що має посилання на об'єкт.

x = 1;          // Тепер об'єкт, що початково був присвоєний 'x', має унікальне посилання,
                // втілене у змінній 'y'.

var z = y.a;    // Посилання на властивість 'a' об'єкта.
                //   Цей об'єкт тепер має 2 посилання: одне у вигляді властивості,
                //   інше у вигляді змінної 'z'.

y = 'mozilla';  // Об'єкт, що початково був присвоєний 'x', тепер має нуль посилань.
                //   Він може бути прибраний.
                //   Однак, на його властивість 'a' досі посилається
                //   змінна 'z', тому її не можна вивільнити.

z = null;       // Властивість 'a' об'єкта, що початково був присвоєний x,
                //   має нуль посилань. Об'єкт тепер може бути прибраний.
</pre>

<h4 id="Обмеження_Циклічні_посилання">Обмеження: Циклічні посилання</h4>

<p>Існує обмеження у випадку циклічних посилань. У наступному прикладі два об'єкти створені з властивостями, в яких вони посилаются один на одного, таким чином створюючи цикл. Вони вийдуть за межі області видимості, коли завершиться виклик функції. В цей момент вони стають непотрібними, і їхня виділена пам'ять має бути повернена. Однак, алгоритм, що підраховує посилання, не вважатиме їх готовими для повернення, оскільки кожен з двох об'єктів має принаймні одне посилання, що вказує на нього, в результаті жоден з них не позначається для збирання сміття. Циклічні посилання є типовою причиною витоків пам'яті.</p>

<pre class="brush: js">function f() {
  var x = {};
  var y = {};
  x.a = y;        // x посилається на y
  y.a = x;        // y посилається на x

  return 'йцуке';
}

f();
</pre>

<h4 id="Приклад_з_реального_життя">Приклад з реального життя</h4>

<p>Internet Explorer 6 та 7 відомі тим, що їхні збирачі сміття для об'єктів DOM працюють на основі підрахунку посилань. Цикли є типовою помилкою, що призводить до витоків пам'яті:</p>

<pre class="brush: js">var div;
window.onload = function() {
  div = document.getElementById('myDivElement');
  div.circularReference = div;
  div.lotsOfData = new Array(10000).join('*');
};
</pre>

<p>У наведеному прикладі DOM-елемент "myDivElement" має циклічне посилання на самого себе у властивості "circularReference". Якщо ця властивість не буде явно видалена чи обнулена, збирач сміття, що підраховує посилання, завжди матиме принаймні одне наявне посилання і триматиме DOM-елемент у пам'яті, навіть якщо він був видалений з DOM-дерева. Якщо DOM-елемент містить великий об'єм даних (проілюстровано у наведеному прикладі властивістю "lotsOfData"), пам'ять, зайнята цими даними, ніколи не буде вивільнена, що може призвести до проблем, пов'язаних з пам'яттю, наприклад, переглядач ставатиме все повільнішим.</p>

<h3 id="Алгоритм_маркування_та_прибирання_mark-and-sweep">Алгоритм маркування та прибирання (mark-and-sweep)</h3>

<p>Цей алгоритм звужує визначення "об'єкта, який більше не потрібен" до "недосяжного об'єкта".</p>

<p>Даний алгоритм використовує набір об'єктів, що називаються <em>коренями. </em>У JavaScript корінь є глобальним об'єктом. Періодично збирач сміття починатиме від цих коренів, знаходитиме усі об'єкти, що мають посилання у коренях, далі усі об'єкти, що мають посилання у них, і т. д. Таким чином, починаючи від коренів, збирач сміття знайде усі <em>досяжні</em> об'єкти та збере усі недосяжні об'єкти.</p>

<p>Цей алгоритм є покращенням попереднього, оскільки об'єкт, що має нуль посилань, є об'єктивно недосяжним. Протилежне не є істиною, як ми бачили на прикладі циклічних посилань.</p>

<p>Станом на 2012 рік усі сучасні переглядачі використовують збирачі сміття з алгоритмом маркування та прибирання. Усі покращення в галузі збирання сміття у JavaScript (генераційне/інкрементне/конкурентне/паралельне збирання сміття) за останні кілька років є покращеннями реалізації даного алгоритму, а не покращеннями самого алгоритму збирання сміття чи скороченням його визначення моменту, коли "об'єкт більше не потрібен".</p>

<h4 id="Циклічні_посилання_більше_не_є_проблемою">Циклічні посилання більше не є проблемою</h4>

<p>У наведеному вище прикладі, коли завершується виклик функції, два об'єкти більше не мають посилань з жодного ресурсу, досяжного з глобального об'єкта. Як наслідок, збирач сміття визнає їх недосяжними та поверне виділену під них пам'ять.</p>

<h4 id="Обмеження_Ручне_вивільнення_памяті">Обмеження: Ручне вивільнення пам'яті</h4>

<p>Бувають випадки, коли було б зручно власноруч визначити, коли й яка пам'ять буде звільнена. Щоб звільнити пам'ять об'єкта, його треба зробити явно недосяжним.</p>

<p>Станом на 2019 рік немає можливості явно чи програмно запустити збирання сміття у JavaScript.</p>

<h2 id="Node.js">Node.js</h2>

<p>Node.js пропонує додаткові можливості та інструменти для конфігурування та відлагодження проблем з пам'яттю, які можуть бути недоступні для JavaScript, що виконується у середовищі переглядача.</p>

<h4 id="Прапори_рушія_V8">Прапори рушія V8</h4>

<p>Максимальний розмір доступної пам'яті купи може бути збільшений за допомогою прапору:</p>

<p><code>node --<em>max-old-space-size=6000</em> index.js</code></p>

<p>Ми також можемо викликати збирач сміття для відлагодження проблем з пам'яттю, використовуючи прапор та <a href="https://nodejs.org/uk/docs/guides/debugging-getting-started/">Chrome Debugger</a>:</p>

<pre class="brush: bash">node --expose-gc --inspect index.js</pre>

<h4 id="Див._також">Див. також</h4>

<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="/uk/docs/Mozilla/Performance" title="https://developer.mozilla.org/en-US/docs/Mozilla/Performance">Продуктивність</a></li>
</ul>