Zarządzanie pamięcią

Garbage Collector

Podczas pracy nad kodem non stop używamy wszelakich danych, które muszą być przetrzymywane w pamięci.

W Javascript podobnie do wielu języków wysokopoziomowych stosuje się automatyczne zarządzanie pamięcią czyli mechanizm zwany Garbage Collection. Oznacza to, że gdy tworzymy nowe obiekty, Javascript automatycznie przydziela im pamięć, a gdy nie są już przez nas używane, są one z niej automatycznie usuwane.

Przydzielanie pamięci nie jest raczej rzeczą trudną (...), ale jak sprawdzić co możemy w danej chwili usunąć by nie zepsuć działania programu?

Do takiego sprawdzania wykorzystywane są różne algorytmy, które opierają swoje działanie na pojęciu "dostępności obiektów". Oznacza ono, że jeżeli do danego obiektu nie można się w żaden sposób odwołać (nie ma do niego żadnej referencji), staje się on nieużywalny, a więc i może być usunięty z pamięci.

W dzisiejszych czasach (a w zasadzie gdzieś od 2012 roku) w Javascript wykorzystywany jest algorytm zwany "mark-and-sweep".

Algorytm ten zakłada istnienie głównych obiektów (roots). Dla języka Javascript takim obiektem jest główny obiekt (dla przeglądarki Window).

Garbage Collector cyklicznie co jakiś czas rozpoczyna swoją pracę od głównego obiektu. Oznacza go jako obiekt z referencją (mark). Następnie poprzez jego odwołania przechodzi do innych obiektów - je także oznaczając. Następnie GC sprawdza te obiekty i ich odwołania do kolejnych obiektów, oznacza je i tak dalej, aż dojdzie do końca. Po oznaczeniu wszystkich obiektów do których prowadzą jakiekolwiek referencje, GC zaczyna usuwać obiekty nieużywalne, do których nie było żadnych odwołań.

W realnym świecie powyższe działanie jest o wiele bardziej skomplikowane, ponieważ przez ostatnie lata zostało wprowadzone do silnika Javascript wiele usprawnień, które powodują, że cały proces jest szybszy i nie blokuje działania skryptów.

Tak naprawdę na co dzień nie musisz znać bardzo dokładnie wszystkich mechanizmów jakie działają w tle - tym bardziej, że przeglądarki się zmieniają i wprowadzają tutaj swoje własne rozwiązania.

Jeżeli temat cię zainteresował, polecam zapoznać się z artykułami: https://blog.sessionstack.com/how-javascript-works-memory-management-how-to-handle-4-common-memory-leaks-3f28b94cfbec i https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management

Przykłady działania

Jak to wygląda od strony kodu? Stwórzmy przykładowo prostą zmienną z obiektem:


let ob = {
    name : "Karol"
}

Jeżeli teraz pod zmienną ob podstawimy coś innego np.:


ob = null; //nie musi to być null

to obiekt na który wskazywała poprzednio nasza zmienna nie będzie już w żaden sposób dla nas dostępny i Garbage Collector usunie go z pamięci.

Jeżeli jednak istniała będzie jakakolwiek referencja do danego obiektu, Garbage Collector nie usunie go z pamięci.


let ob = {
    name : "Karol"
}

//druga zmienna która wskazuje na ten sam obiekt
let ob2 = ob;

ob = null;
console.log(ob2); //{name : "Karol"}

W powyższym kodzie ustawiliśmy ob na null, ale zmienna ob2 dalej wskazuje na obiekt będący w pamięci. Z tego też powodu nie zostanie on usunięty przez Garbage Collector.

Podobna sytuacja może pojawić się jeszcze w kilku momentach - np. gdy dany obiekt wstawimy do obiektu typu Map, czy tablicy:


let ob = {
    name : "Karol"
}

const tab = [ob];
ob = null;
console.log(tab[0]); //{name : "Karol"}

Znowu - ustawienie ob na null nic w tym przypadku nie da, ponieważ dalej możemy odwołać się do danego obiektu poprzez tablicę czyli zapis tab[0].

Kolejny przykład, gdzie GC nie będzie mógł wykonać swojego zadania pokazuje poniższy kod:


//1
function test() {
    myName = "kot Rudzik";
}
test();

//2
for (i=0; i<100; i++) {
    console.log("Licznik wynosi: " + i);
}

//3
const tab = ["ala", "bala", "cala"];
for (el of tab) {
    console.log(el);
}

Czy widzisz tutaj błędy?

Niestety w żadnym z trzech przypadków GC nie będzie mógł usunąć zmiennych z pamięci. Wynika to z faktu, że przy tworzeniu zmiennych myName, i i el z własnej wygody (a może i lenistwa) nie użyliśmy słów kluczowych let/const. A przecież umówiliśmy się, że będziemy tak robić... Takie działanie spowodowało, że za każdym razem dostajemy zmienną globalną, która przypięta jest do obiektu Window, a więc nie będzie mogła być usunięta.


console.log(window.myName) //"kotRudzik"
console.log(window.i); //100
console.log(window.el); //"cala"

Warto tutaj zaznaczyć, że poprawa działania GC jest jednym z powodów wprowadzanie do Javascript let/const. Dzięki nim oraz odpowiedniemu używaniu bloków automatyczne oczyszczanie pamięci w większości przypadków będzie dla nas wystarczającym rozwiązaniem:


{
    let ob = {
        name : "Karol"
    }

    const tab = [ob];
    ob = null;
    console.log(tab[0]); //{name : "Karol"}
}

console.log(tab[0]); //poza blokiem już nie ma tablicy
console.log(ob); //tym bardziej obiektu

Trening czyni mistrza

Jeżeli chcesz sobie potrenować zdobytą wiedzę z tego działu, to zadania znajdują się w repozytorium pod adresem: https://github.com/kartofelek007/zadania-obiekty

W repozytorium jest branch "solutions". Tam znajdziesz przykładowe rozwiązania.

Wszelkie prawa zastrzeżone. Jeżeli chcesz używać jakiejś części tego kursu, skontaktuj się z autorem.
Aha - i ta strona korzysta z ciasteczek.

Menu