През последното десетилетие достъпността до интернет претърпя голяма промяна от само „привилегированата класа“ към почти всеки, който притежава устройство, което може да се свързва с интернет. С все повече и повече хора, които имат достъп до интернет, разработчиците са изправени пред голямото предизвикателство да управляват натоварването на приложението. Изтичането на памет е най-често срещаният враг, с който технологичната индустрия трябва да се бори всеки ден с нарастващата потребителска база и освен това с нарастваща кодова база с тонове функции, добавени всяка друга версия на софтуера. Изтичането на памет може да доведе до проблеми като забавяне на приложението, сривове, висока латентност и т.н.

За езици на ниско ниво като C, разработчиците трябва ръчно да извършват управление на паметта с malloc и calloc. За разлика от това, JavaScript автоматично разпределя памет, когато обектите се създават, и я освобождава, когато не се използват повече. Този процес е известен като Събиране на боклук. Но това означава ли, че разработчиците на javascript живеят без познания за управление на паметта? Определено не! Всеки път, когато има лошо отразяване на Node.js в пресата, това (обикновено) е свързано с проблеми с производителността. Потребителят просто трябва да е наясно с определени неща за това как работи Node.js.

Независимо от езика за програмиране, цикълът на събиране на боклука е почти подобен.

Събиране на отпадъци за преброяване на справки

Това е най-наивният алгоритъм за събиране на боклук. Този алгоритъм определя дали има поне една препратка към обекта. За един обект се казва, че е „боклук“, ако има нула препратки, сочещи към него. Нека да видим като цяло как работи GC.

Ограничение: Циркулярни препратки

да кажем, че има два обекта в паметта, които се препращат един към друг, като по този начин създават цикъл, но са отделени от корена. Тоест те са извън обхвата на функцията и вече не са необходими при изпълнението на кода. Сега тази памет трябва да бъде възстановена. Въпреки това, тъй като алгоритъмът за преброяване на препратки работи, като проверява дали даден обект има поне препратка, сочеща към тях, нито един от тях не се маркира като боклук и продължава да заема място в паметта.

Обърнете внимание, че кръгово реферираните възли не се премахват от паметта.

Алгоритъм за маркиране и почистване

Този алгоритъм редуцира дефиницията на „обект вече не е необходим“ до „обект е недостъпен“. Този алгоритъм предполага познаването на набори от обекти, наречени корени. В Javascript root е глобален обект. По този начин събирачът на боклук ще започне от корените, ще намери всички обекти, посочени от тези корени, и обектите, посочени от тези и така нататък. По този начин намира достъпни и недостъпни обекти.

След това GC освобождава недостъпните обекти от паметта. Това решава проблема с кръговите препратки. Ако два обекта с кръгови препратки съществуват в паметта и не са достъпни директно или индиректно през корените, тогава те се освобождават от паметта. Този алгоритъм е допълнително обяснен подробно в раздела за управление на паметта Node.js.

Управление на паметта в Nodejs

Google V8 е JavaScript двигател, първоначално създаден за Google Chrome, но може да се използва и като самостоятелен. Това го прави идеалното решение за Node.js. V8 компилира JavaScript до естествен код и го изпълнява.

Схема на паметта на V8

Работеща програма се представя чрез пространство в паметта, наречено Резидентен набор. Това разделя паметта на определени групи:

Код: Действително изпълняваният код

Стек: Съдържа статични данни като функционални рамки, примитивни стойности (като булево/цяло число), указатели към обекти в купчината и др.

Група: V8 съхранява обекти или динамични данни в купчина. Това е най-големият блок от областта на паметта и там се извършва събирането на боклука (GC).

Съвет: Текущото използване на паметта в Node.js може да се провери чрез извикване на process.memoryUsage().

Стек

Разгледайте следния код:

Тук и a, и b ще бъдат поставени в стека.

Купчина

Сега разгледайте следния код:

След това Heap паметта ще изглежда по следния начин:

Сега нека още два обекта:

нашата памет се актуализира до:

Ако GC стартира сега, нищо няма да бъде освободено, тъй като коренът е свързан с всички обекти в паметта. Това казва на GC, че всички обекти, дефинирани в изпълнявания код, се използват за изпълнение.

Помислете, вече не използваме обект Pizza. Да приемем, че сме задали Pizza да бъде недефинирана. (Моля, имайте предвид, че за да промените стойността на Pizza, трябва да използвате ключова дума „let“, за да направите първоначалната дефиниция на Pizza, а не const)

Сега паметта ще изглежда така:

Сега, когато GC се изпълнява, оригиналният обект Pizza не може да бъде достигнат от основния обект, така че при следващото изпълнение на събирача на отпадъци той ще бъде освободен:

Какво причинява изтичане на памет в JS

Така че най-голямата дилема е, ако JS вече има автоматизиран събирач на боклук, тогава защо разработчикът трябва да научава за изтичане на памет? Javascript все още има ли течове на памет? Отговорът е да.

Въпреки че автоматичното управление на паметта като събиране на боклук във V8 избягва повечето от течовете на памет, все още може да има нежелани препратки към паметта в купчината, което може да се случи поради различни причини.

Глобални променливи: Тъй като глобалните променливи в JS съществуват в глобалния контекст на изпълнение (GEC), тези променливи винаги се препращат от основния възел (прозорец или глобален this) те никога не се събират за боклук през целия живот на приложението и ще заемат памет. Наличието на голяма графика на обекти, посочени от корена, може да причини изтичане на памет.

Множество препратки: Възможно е да има случай, при който един и същ обект се препраща от множество обекти и една от препратките остава висяща.

Затваряния: Затварянето на Javascript е отличен инструмент, който помага при запаметяването на неговия контекст. Когато затварянето съдържа препратка към голям обект в купчината, обектът остава в паметта, докато това конкретно затваряне се използва. Ако затварянията се използват неправилно, това може да доведе до изтичане на памет.

Таймери и събития: Използването на setTimeout, setInterval, наблюдатели и слушатели на събития може да причини изтичане на памет, ако препратките към големи обекти се съхраняват в обратното им извикване без подходяща обработка.

първоначално публикувано в amodshinde.com