Co to jest czyszczenie pamięci JavaScript?

Odpowiedzi:

192

Eric Lippert napisał jakiś szczegółowy blog na ten temat jakiś czas temu (dodatkowo porównując go do VBScript ). Dokładniej napisał o JScript , który jest własną implementacją ECMAScript Microsoftu, chociaż bardzo podobną do JavaScript. Wyobrażam sobie, że można założyć, że zdecydowana większość zachowań byłaby taka sama dla silnika JavaScript przeglądarki Internet Explorer. Oczywiście implementacja będzie się różnić w zależności od przeglądarki, ale podejrzewam, że możesz wziąć wiele wspólnych zasad i zastosować je w innych przeglądarkach.

Cytat z tej strony:

JScript korzysta z nieenereneracyjnego zbieracza śmieci typu mark-and-sweep. Działa to tak:

  • Każda zmienna, która jest „w zasięgu”, nazywana jest „wymiataczem”. Zmiatacz może odnosić się do liczby, obiektu, łańcucha, cokolwiek innego. Prowadzimy listę zmiataczy - zmienne są przenoszone na listę zmiatania, gdy wchodzą w zakres, i z listy, gdy wychodzą z zakresu.

  • Od czasu do czasu działa śmietnik. Najpierw umieszcza „znak” na każdym obiekcie, zmiennej, łańcuchu itp. - całej pamięci śledzonej przez GC. (JScript korzysta wewnętrznie ze struktury danych VARIANT i jest w niej wiele dodatkowych nieużywanych bitów, więc właśnie ustawiliśmy jeden z nich.)

  • Po drugie, usuwa ślad na padlinożercach i przechodnie przechodzenie odniesień do padlinożerców. Więc jeśli obiekt zmiatający odwołuje się do obiektu nieużywającego, wówczas usuwamy bity na nienaśmiecającym i na wszystkim, do czego się odnosi. (Używam słowa „zamknięcie” w innym znaczeniu niż w moim wcześniejszym poście).

  • W tym momencie wiemy, że cała pamięć wciąż zaznaczona to pamięć przydzielona, ​​do której nie można dotrzeć żadną ścieżką z żadnej zmiennej in-scope. Wszystkie te obiekty mają się rozerwać, co niszczy wszelkie okrągłe odniesienia.

Głównym celem wyrzucania elementów bezużytecznych jest to, aby programiści nie martwili się zarządzaniem pamięcią obiektów, które tworzą i używają, choć oczywiście czasami nie da się tego uniknąć - zawsze dobrze jest mieć przynajmniej ogólne pojęcie o działaniu wyrzucania elementów bezużytecznych .

Uwaga historyczna: wcześniejsza wersja odpowiedzi zawierała niepoprawne odniesienie do deleteoperatora. W JavaScript operator usuwa własności z przedmiotu i jest całkowicie inny niż C / C ++.deletedelete

Noldorin
źródło
27
przewodnik Apple jest wadliwy: autor używa deletenieprawidłowo; np. w pierwszym przykładzie, zamiast delete foo, należy najpierw usunąć detektor zdarzeń za pomocą, window.removeEventListener()a następnie użyć foo = nulldo zastąpienia zmiennej; w IE delete window.foo(ale nie delete foo) również działałby, gdyby foobył globalny, ale nawet wtedy nie działałby w FF i Operze
Christoph
3
Pamiętaj, że artykuł Erica należy traktować „wyłącznie do celów historycznych”. Ale wciąż ma charakter informacyjny.
Peter Ivan
2
Zauważ też - IE 6 i 7 NIE używają nieeneracyjnego generatora śmieci. Używają prostego licznika odwołań do odśmiecania pamięci, który jest bardziej podatny na problemy z odwołaniami cyklicznymi przy usuwaniu śmieci.
Doug
1
ECMAScript's deletejest jednoargumentowym operatorem (wyrażenie), a nie instrukcją (tj .:) delete 0, delete 0, delete 3. Wygląda jak instrukcja wyrażona przez wyrażenie wyrażeniowe.
Hydroper
Tak, odpowiedź w tym czasie jest obecnie nieaktualna, od 2012 roku nowoczesne przeglądarki używają algorytmu mark / sweep .. więc nie jest już zależny od zakresu. Odwołanie: developer.mozilla.org/en-US/docs/Web/JavaScript/…
sksallaj
52

Uwaga na odnośniki cykliczne, gdy w grę wchodzą obiekty DOM:

Wzorce wycieków pamięci w JavaScript

Pamiętaj, że pamięć można odzyskać tylko wtedy, gdy nie ma aktywnych odniesień do obiektu. Jest to częsta pułapka w przypadku zamknięć i procedur obsługi zdarzeń, ponieważ niektóre silniki JS nie sprawdzają, które zmienne są faktycznie przywoływane w funkcjach wewnętrznych, a jedynie zachowują wszystkie zmienne lokalne funkcji zamykających.

Oto prosty przykład:

function init() {
    var bigString = new Array(1000).join('xxx');
    var foo = document.getElementById('foo');
    foo.onclick = function() {
        // this might create a closure over `bigString`,
        // even if `bigString` isn't referenced anywhere!
    };
}

Naiwna implementacja JS nie może się zbierać, bigStringdopóki istnieje moduł obsługi zdarzeń. Istnieje kilka sposobów rozwiązania tego problemu, np. Ustawienie bigString = nullna końcu init()( deletenie działa dla zmiennych lokalnych i argumentów funkcji: deleteusuwa właściwości z obiektów, a obiekt zmiennej jest niedostępny - ES5 w trybie ścisłym wyrzuci nawet ReferenceErrorjeśli spróbujesz usunąć zmienną lokalną!).

Zalecam unikanie niepotrzebnego zamykania, jeśli zależy Ci na zużyciu pamięci.

Christoph
źródło
20
Błąd odwołania cyklicznego DOM jest specyficzny dla JScript - żadna inna przeglądarka nie cierpi z wyjątkiem IE. W rzeczywistości jestem całkiem pewien, że specyfikacja ECMAScript wyraźnie stwierdza, że ​​GC musi być w stanie poradzić sobie z takimi cyklami: - /
olliej
@olliej: Nie widzę żadnej wzmianki o GC w specyfikacji ECMAScript .
Janus Troelsen
patrz także point.davidglasser.net/2013/06/27/…
Christoph
16

Dobry cytat z bloga

Składnik DOM jest „odśmiecany”, podobnie jak składnik JScript, co oznacza, że ​​jeśli utworzysz obiekt w którymkolwiek z komponentów, a następnie stracisz kontakt z tym obiektem, zostanie on ostatecznie wyczyszczony.

Na przykład:

function makeABigObject() {
var bigArray = new Array(20000);
}

Po wywołaniu tej funkcji komponent JScript tworzy obiekt (o nazwie bigArray), który jest dostępny w obrębie funkcji. Gdy jednak funkcja powróci, „tracisz orientację” bigArray, ponieważ nie ma już możliwości odwoływania się do niego. Cóż, komponent JScript zdaje sobie sprawę, że go zgubiłeś, a więc bigArray został oczyszczony - jego pamięć została odzyskana. To samo działa w komponencie DOM. Jeśli powiesz document.createElement('div')lub coś podobnego, komponent DOM tworzy dla Ciebie obiekt. Gdy w jakiś sposób stracisz kontakt z tym obiektem, komponent DOM wyczyści powiązane.

TStamper
źródło
13

Zgodnie z moją najlepszą wiedzą, obiekty JavaScript są okresowo usuwane, gdy nie ma żadnych odwołań do obiektu. Jest to coś, co dzieje się automatycznie, ale jeśli chcesz dowiedzieć się więcej o tym, jak to działa, na poziomie C ++ warto spojrzeć na kod źródłowy WebKit lub V8

Zazwyczaj nie trzeba o tym myśleć, jednak w starszych przeglądarkach, takich jak IE 5.5 i wczesne wersje IE 6, i być może w obecnych wersjach, zamknięcia tworzyłyby cykliczne odwołania, które po odznaczeniu powodowałyby zużycie pamięci. W konkretnym przypadku, który mam na myśli w przypadku zamknięć, było to wtedy, gdy dodałeś odwołanie JavaScript do obiektu dom i obiekt do obiektu DOM, który odwoływał się z powrotem do obiektu JavaScript. Zasadniczo nigdy nie można go było zebrać, co ostatecznie spowodowałoby niestabilność systemu operacyjnego w aplikacjach testowych, które zapętlały się w celu utworzenia awarii. W praktyce przecieki te są zwykle niewielkie, ale aby utrzymać kod w czystości, należy usunąć odwołanie JavaScript do obiektu DOM.

Zwykle dobrym pomysłem jest użycie słowa kluczowego delete, aby natychmiast odwołać odwołanie do dużych obiektów, takich jak dane JSON, które otrzymałeś i zrobić wszystko, co musisz z tym zrobić, szczególnie w tworzeniu aplikacji mobilnych. Powoduje to, że następny przegląd GC usunie ten obiekt i zwolni jego pamięć.

Nędzarz ciepła
źródło
Czy problem z odwołaniem cyklicznym JavaScript -> DOM -> JavaScript jest rozwiązany w nowszych wersjach IE? Jeśli tak, to od kiedy? Myślałem, że jest to architektonicznie bardzo głęboko i mało prawdopodobne, aby kiedykolwiek zostało naprawione. Czy masz jakieś źródła?
erikkallen
Po prostu anegdotycznie. Nie zauważyłem szalonych przecieków w IE 8 działającym w trybie standardowym, a nie w trybie zepsutym. Dostosuję swoją odpowiedź.
Heat Miser
1
@erikkallen: tak, błąd GC został naprawiony w IE w wersji 8+, ponieważ starsze wykorzystywały bardzo naiwny algorytm odśmiecania, co uniemożliwiło GC parę obiektów odnoszących się do siebie. Nowsze mark-and-sweepalgorytmy stylu tym zająć .
kumarharsh
6

odśmiecanie pamięci (GC) jest formą automatycznego zarządzania pamięcią poprzez usuwanie niepotrzebnych już obiektów.

każdy proces zajmujący się pamięcią wykonaj następujące kroki:

1 - przydziel potrzebne miejsce w pamięci

2 - wykonaj przetwarzanie

3 - zwolnij to miejsce w pamięci

istnieją dwa główne algorytmy do wykrywania, które obiekty nie są już potrzebne.

Wyrzucanie elementów bezużytecznych : ten algorytm redukuje definicję „obiekt nie jest już potrzebny” do „obiekt nie ma innego obiektu, do którego się odwołuje”, obiekt zostanie usunięty, jeśli nie ma do niego punktu odniesienia

Algorytm oznaczania i przeciągania : podłącz każdy obiekt do źródła root. żaden obiekt nie łączy się z rootem ani innym obiektem. ten obiekt zostanie usunięty.

obecnie większość nowoczesnych przeglądarek używa drugiego algorytmu.

Ahmed Gaber - Biga
źródło
1
Aby dodać źródło tego, zobacz MDN: developer.mozilla.org/en-US/docs/Web/JavaScript/...
Xenos
4

„W informatyce wyrzucanie elementów bezużytecznych (GC) jest formą automatycznego zarządzania pamięcią. Moduł wyrzucania elementów bezużytecznych, lub po prostu moduł zbierający, próbuje odzyskać elementy wyrzucające śmieci lub pamięć wykorzystywaną przez obiekty, do których aplikacja nigdy nie uzyska dostępu ani nie zostanie zmutowana”.

Wszystkie silniki JavaScriptu mają własne urządzenia do zbierania śmieci i mogą się różnić. Przez większość czasu nie musisz sobie z nimi radzić, ponieważ robią to, co powinni.

Pisanie lepszego kodu zależy głównie od tego, jak dobrze znasz zasady programowania, język i konkretną implementację.

mtasic85
źródło
1

Co to jest czyszczenie pamięci JavaScript?

sprawdź to

Co jest ważne dla programisty internetowego, aby rozumieć zbieranie śmieci JavaScript w celu napisania lepszego kodu?

W Javascript nie przejmujesz się przydziałem i dezalokacją pamięci. Cały problem jest wymagany od interpretera Javascript. Wycieki są nadal możliwe w JavaScript, ale są to błędy interpretera. Jeśli jesteś zainteresowany tym tematem, możesz przeczytać więcej na www.memorymanagement.org

dfa
źródło
Który z różnych systemów zarządzania pamięcią w artykule, do którego linkujesz, jest używany przez JavaScript? „W JavaScript są nadal możliwe wycieki, ale są to błędy interpretera”. - To nie znaczy, że programiści JS mogą po prostu zignorować cały problem, na przykład istnieje dość dobrze znany problem odwołania cyklicznego JS <-> DOM w starszych wersjach IE, który można obejść w kodzie JS. Ponadto sposób, w jaki działają zamknięcia JS, jest cechą projektową, a nie błędem, ale możesz powiązać większe fragmenty pamięci niż zamierzone, jeśli użyjesz zamknięć „niewłaściwie” ( nie mówię, że ich nie używaj).
nnnnnn
3
Wycieki pamięci są bestią w JavaScript. Jeśli piszesz prostą aplikację „college college”, nie martw się. Ale kiedy zaczynasz pisać wysokowydajne aplikacje na poziomie korporacyjnym, zarządzanie pamięcią w JavaScript jest koniecznością.
Doug
1

W systemie Windows możesz użyć narzędzia Drip.exe, aby znaleźć wycieki pamięci lub sprawdzić, czy działa darmowa procedura mem.

To naprawdę proste, wystarczy wpisać adres URL strony, a zobaczysz zużycie pamięci przez zintegrowany renderer IE. Następnie naciśnij przycisk odświeżania, jeśli pamięć się zwiększy, zauważyłeś wyciek pamięci gdzieś na stronie internetowej. Ale jest to również bardzo przydatne, aby sprawdzić, czy procedury zwalniania pamięci działają w IE.

powtac
źródło
1

Typy referencyjne nie przechowują obiektu bezpośrednio w zmiennej, do której jest przypisany, więc zmienna obiektowa w tym przykładzie tak naprawdę nie zawiera instancji obiektu. Zamiast tego zawiera wskaźnik (lub odniesienie) do miejsca w pamięci, w którym istnieje obiekt

var object = new Object();

jeśli przypiszesz jedną zmienną do drugiej, każda zmienna otrzyma kopię wskaźnika i obie nadal będą odwoływać się do tego samego obiektu w pamięci.

var object1 = new Object();
var object2 = object1;

Dwie zmienne wskazujące na jeden obiekt

JavaScript jest językiem zbierającym śmieci , więc tak naprawdę nie musisz martwić się o przydział pamięci podczas korzystania z typów referencyjnych. Jednak najlepiej wyrejestrować obiekty, których już nie potrzebujesz, aby śmieciarz mógł zwolnić tę pamięć. Najlepszym sposobem na to jest ustawienie zmiennej obiektowej na null.

var object1 = new Object();
// do something
object1 = null; // dereference

Obiekty dereferencyjne są szczególnie ważne w bardzo dużych aplikacjach, które wykorzystują miliony obiektów.

z zasad JavaScript obiektowego - NICHOLAS C. ZAKAS

Ani Naslyan
źródło