Jak usuwane są śmieci z zamknięć JavaScript

Zarejestrowałem następujący błąd Chrome , który doprowadził do wielu poważnych i nieoczywistych wycieków pamięci w moim kodzie:

(te wyniki wykorzystują narzędzia Chrome Dev Tools ' memory profiler , który uruchamia GC, a następnie wykonuje migawkę sterty wszystkiego, co nie zostało zebrane.)

W poniższym kodzie, instancja someClass jest garbage collected (good):

var someClass = function() {};

function f() {
  var some = new someClass();
  return function() {};
}

window.f_ = f();

Ale to nie będą śmieci zbierane w tym przypadku (złe):

var someClass = function() {};

function f() {
  var some = new someClass();
  function unreachable() { some; }
  return function() {};
}

window.f_ = f();

I odpowiednie zrzut ekranu:

zrzut ekranu Chromebug

Wydaje się, że zamknięcie (w tym przypadku function() {}) utrzymuje wszystkie obiekty "przy życiu", jeśli do obiektu odnosi się jakiekolwiek inne zamknięcie w tym samym kontekście, niezależnie od tego, czy samo zamknięcie jest osiągalne, czy nie.

Moje pytanie dotyczy zbierania śmieci w innych przeglądarkach (IE 9+ i Firefox). Jestem całkiem zaznajomiony z narzędziami webkita, takimi jak JavaScript heap profiler, ale niewiele wiem o narzędziach innych przeglądarek, więc nie byłem w stanie przetestować to.

W którym z tych trzech przypadków IE9 + i Firefox garbage zbierają someClass przykład?

Author: Paul Draper, 2013-11-06

6 answers

Z tego co wiem, to nie jest błąd, ale oczekiwane zachowanie.

[[9]}ze strony Mozilli zarządzanie pamięcią : "od 2012 roku wszystkie nowoczesne przeglądarki wysyłają mark-and-sweep-garbage-collector." " ograniczenie: obiekty muszą być wyraźnie nieosiągalne".

W Twoich przykładach, gdzie się nie powiedzie some jest nadal osiągalny w zamknięciu. Próbowałem dwóch sposobów, aby było nieosiągalne i oba działają. Albo ustawiasz some=null Kiedy już go nie potrzebujesz, albo ustaw window.f_ = null; i zniknie.

Update

Próbowałem go w Chrome 30, FF25, Opera 12 i IE10 na Windows.

Standard nie mówi nic o zbiórce śmieci, ale daje pewne wskazówki, co powinno się wydarzyć.

  • sekcja 13 definicja funkcji, krok 4: "niech zamknięcie będzie wynikiem utworzenia nowego obiektu funkcji, jak określono w 13.2"
  • Section 13.2 " a Lexical Environment specified by Scope "(scope = zamknięcie)
  • Sekcja 10.2 Środowiska Leksykalne:

"zewnętrzne odniesienie (wewnętrznego) środowiska leksykalnego jest odniesieniem do środowiska leksykalnego, które logicznie otacza wewnętrzne środowisko leksykalne.

Zewnętrzne środowisko leksykalne może oczywiście mieć swoje zewnętrzne Środowisko Leksykalne. Środowisko leksykalne może służyć jako środowisko zewnętrzne dla wielu wewnętrznych leksykalnych Środowisk. Na przykład, jeśli deklaracja funkcji zawiera dwie zagnieżdżone deklaracje funkcji następnie leksykalne Środowiska każdej z funkcji zagnieżdżonych będą miały jako swoje zewnętrzne środowisko leksykalne leksykalne Środowisko bieżącego wykonywania funkcji otaczającej."

Tak więc funkcja będzie miała dostęp do środowiska rodzica.

Tak więc, some powinny być dostępne w zamknięciu funkcji zwracającej.

Więc dlaczego nie jest zawsze dostępny?

Wydaje się, że Chrome i FF jest na tyle inteligentny, aby wyeliminować zmienną w niektórych przypadkach, ale zarówno w Operze, jak i IE zmienna some jest dostępna w zamykaniu (uwaga: aby to zobaczyć, ustaw punkt przerwania na return null i sprawdź debugger).

GC może być ulepszony do wykrywania, czy some jest używany lub nie w funkcjach, ale będzie to skomplikowane.

Zły przykład:

var someClass = function() {};

function f() {
  var some = new someClass();
  return function(code) {
    console.log(eval(code));
  };
}

window.f_ = f();
window.f_('some');

W powyższym przykładzie GC nie ma możliwości sprawdzenia czy zmienna jest używana czy nie (kod testowany i działa w Chrome30, FF25, Opera 12 i IE10).

Pamięć jest zwalniana, jeśli odwołanie do obiektu zostanie przerwane przez przypisanie innej wartości do window.f_.

Moim zdaniem to nie jest błąd.
 77
Author: some,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-13 04:38:09

Testowałem to w IE9+ i Firefoksie.

function f() {
  var some = [];
  while(some.length < 1e6) {
    some.push(some.length);
  }
  function g() { some; } //removing this fixes a massive memory leak
  return function() {};   //or removing this
}

var a = [];
var interval = setInterval(function() {
  var len = a.push(f());
  if(len >= 500) {
    clearInterval(interval);
  }
}, 10);

Live site tutaj .

Miałem nadzieję skończyć z tablicą 500 function() {} ' s, przy użyciu minimalnej pamięci.

Niestety tak nie było. Każda pusta funkcja posiada (na zawsze nieosiągalną, ale nie GC ' ed) tablicę miliona liczb.

Chrome w końcu zatrzymuje się i umiera, Firefox kończy całość po zużyciu prawie 4GB PAMIĘCI RAM, a IE rośnie asymptotycznie wolniej, aż pokaże " z pamięć".

Usunięcie jednej z linii komentarza naprawia wszystko.

Wydaje się, że wszystkie trzy z tych przeglądarek (Chrome, Firefox i IE) przechowują zapis środowiska według kontekstu, a nie zamknięcia. Boris stawia hipotezę, że powodem tej decyzji jest wydajność, a to wydaje się prawdopodobne, choć nie jestem pewien, jak wydajność można ją nazwać w świetle powyższego eksperymentu.

Jeśli potrzebujesz zamknięcia some (przyznam, że nie użyłem go tutaj, ale wyobraź sobie, że tak), jeśli zamiast

function g() { some; }

Używam

var g = (function(some) { return function() { some; }; )(some);

Naprawi problemy z pamięcią, przenosząc zamknięcie do innego kontekstu niż moja inna funkcja.

To uczyni moje życie bardziej nudnym.

P. S. z ciekawości próbowałem tego w Javie (wykorzystując jego zdolność do definiowania klas wewnątrz funkcji). GC działa tak, jak pierwotnie miałem nadzieję na Javascript.

 48
Author: Paul Draper,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-08 03:00:46

Heurystyka jest różna, ale powszechnym sposobem implementacji tego typu rzeczy jest utworzenie rekordu środowiska dla każdego wywołania f() w Twoim przypadku i przechowywanie tylko lokalnych f, które są faktycznie zamknięte (przez jakieś zamknięcie) w tym rekordzie środowiska. Wtedy każde zamknięcie utworzone w wywołaniu f utrzymuje przy życiu zapis środowiska. Wierzę, że w ten sposób Firefox implementuje zamknięcia, przynajmniej.

Ma to zalety szybkiego dostępu do zmiennych zamkniętych i prostoty realizacji. Ma on wadę obserwowanego efektu, gdzie krótkotrwałe zamknięcie zamykające się nad pewną zmienną powoduje, że jest utrzymywane przy życiu przez długotrwałe zamknięcie.

Można spróbować utworzyć wiele rekordów środowiska dla różnych zamknięć, w zależności od tego, co faktycznie się zamyka, ale to może się bardzo szybko skomplikować i może powodować problemy z wydajnością i pamięcią...

 15
Author: Boris Zbarsky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-06 04:09:27
  1. utrzymanie stanu pomiędzy wywołaniami funkcji Załóżmy, że masz funkcję add () i chcesz, aby dodała ona wszystkie wartości przekazane do niej w kilku wywołaniach i zwróciła sumę.

Jak add (5); / / zwraca 5

Add(20); / / zwraca 25 (5+20)

Add (3); / / zwraca 28 (25 + 3)

Dwa sposoby można to zrobić pierwszy jest normalny zdefiniować zmienna globalna Oczywiście możesz użyć zmiennej globalnej, aby utrzymać sumę. Ale pamiętaj, że ten koleś zje cię żywcem, jeśli użyjesz globali.

Teraz ostatni sposób używając closure z out define zmienna globalna

(function(){

  var addFn = function addFn(){

    var total = 0;
    return function(val){
      total += val;
      return total;
    }

  };

  var add = addFn();

  console.log(add(5));
  console.log(add(20));
  console.log(add(3));
  
}());
 0
Author: Avinash Maurya,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-12 03:30:38

function Country(){
    console.log("makesure country call");	
   return function State(){
   
    var totalstate = 0;	
	
	if(totalstate==0){	
	
	console.log("makesure statecall");	
	return function(val){
      totalstate += val;	 
      console.log("hello:"+totalstate);
	   return totalstate;
    }	
	}else{
	 console.log("hey:"+totalstate);
	}
	 
  };  
};

var CA=Country();
 
 var ST=CA();
 ST(5); //we have add 5 state
 ST(6); //after few year we requare  have add new 6 state so total now 11
 ST(4);  // 15
 
 var CB=Country();
 var STB=CB();
 STB(5); //5
 STB(8); //13
 STB(3);  //16

 var CX=Country;
 var d=Country();
 console.log(CX);  //store as copy of country in CA
 console.log(d);  //store as return in country function in d
 0
Author: Avinash Maurya,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-12 04:17:25

(function(){

   function addFn(){

    var total = 0;
	
	if(total==0){	
	return function(val){
      total += val;	 
      console.log("hello:"+total);
	   return total+9;
    }	
	}else{
	 console.log("hey:"+total);
	}
	 
  };

   var add = addFn();
   console.log(add);  
   

    var r= add(5);  //5
	console.log("r:"+r); //14 
	var r= add(20);  //25
	console.log("r:"+r); //34
	var r= add(10);  //35
	console.log("r:"+r);  //44
	
	
var addB = addFn();
	 var r= addB(6);  //6
	 var r= addB(4);  //10
	  var r= addB(19);  //29
    
  
}());
 0
Author: Avinash Maurya,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-12 04:26:36