Programing

JavaScript 클로저가 가비지 수집되는 방법

lottogame 2020. 5. 29. 07:54
반응형

JavaScript 클로저가 가비지 수집되는 방법


다음 Chrome 버그를 기록하여 코드에서 심각하고 명백하지 않은 메모리 누수가 많이 발생했습니다.

(이 결과 는 GC를 실행하고 가비지 수집되지 않은 모든 것의 힙 스냅 샷을 생성하는 Chrome Dev Tools의 메모리 프로파일 러 를 사용합니다.)

아래 코드에서 someClass인스턴스는 가비지 수집됩니다 (양호).

var someClass = function() {};

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

window.f_ = f();

그러나이 경우 가비지 수집되지 않습니다 (나쁜).

var someClass = function() {};

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

window.f_ = f();

그리고 해당 스크린 샷 :

Chromebug의 스크린 샷

클로저 (이 경우 function() {})는 객체가 동일한 컨텍스트에서 다른 클로저에 의해 참조되는 경우 클로저 자체에 도달 할 수 있는지 여부에 관계없이 모든 객체를 "생존"상태로 유지 하는 것으로 보입니다 .

내 질문은 다른 브라우저 (IE 9 이상 및 Firefox)의 가비지 수집에 관한 것입니다. JavaScript 힙 프로파일 러와 같은 웹킷 도구에 익숙하지만 다른 브라우저 도구는 거의 모르므로 테스트 할 수 없었습니다.

이 세 가지 경우 중 IE9 +와 Firefox 가비지가 인스턴스를 수집 someClass 합니까?


내가 알 수있는 한, 이것은 버그가 아니라 예상되는 동작입니다.

Mozilla의 메모리 관리 페이지에서 : "2012 년 현재 모든 최신 브라우저는 마크 앤 스윕 가비지 수집기를 제공합니다." "제한 : 객체는 명시 적으로 도달 할 수 없어야 합니다. "

귀하의 예에서 실패한 부분 some은 여전히 ​​폐쇄에 도달 할 수 있습니다. 나는 그것을 도달 할 수 없도록 만들고 두 가지 방법을 시도했습니다. some=null더 이상 필요하지 않을 때 설정 하거나 설정하면 window.f_ = null;사라집니다.

최신 정보

Windows의 Chrome 30, FF25, Opera 12 및 IE10에서 시도했습니다.

표준은 가비지 컬렉션에 대해 아무 말도 있지만, 어떻게해야하는지의 몇 가지 단서를 제공하지 않습니다.

  • 섹션 13 함수 정의, 4 단계 : "13.2에 지정된 새 Function 객체를 만든 결과를 닫습니다."
  • 섹션 13.2 "범위에 의해 지정된 어휘 환경"(범위 = 폐쇄)
  • 섹션 10.2 어휘 환경 :

"(내부) 어휘 환경의 외부 참조는 내부 어휘 환경을 논리적으로 둘러싼 어휘 환경에 대한 참조입니다.

물론 외부 어휘 환경에는 자체 외부 어휘 환경이있을 수 있습니다. 어휘 환경은 여러 내부 어휘 환경의 외부 환경으로 사용될 수 있습니다. 예를 들어, 함수 선언이 두 중첩 포함 함수 선언 후 중첩 된 각 기능의 사전 환경은 외부 환경 어휘 주변 기능의 현재의 사전 실행 환경으로 할 것이다. "

따라서 함수는 부모의 환경에 액세스 할 수 있습니다.

따라서 some반환 기능을 닫을 때 사용할 수 있어야합니다.

그렇다면 왜 항상 사용할 수 없습니까?

Chrome과 FF는 경우에 따라 변수를 제거하기에 충분히 똑똑한 것으로 보이지만 Opera와 IE 모두 some변수를 클로저에서 사용할 수 있습니다 (NB :이 세트를 중단 점을 return null보고 디버거를 확인하십시오).

some기능에 사용 되는지 여부를 감지하도록 GC를 개선 할 수 는 있지만 복잡합니다.

나쁜 예 :

var someClass = function() {};

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

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

위의 예에서 GC는 변수의 사용 여부를 알 수있는 방법이 없습니다 (코드 테스트 및 Chrome30, FF25, Opera 12 및 IE10에서 작동).

에 다른 값을 할당하여 객체에 대한 참조가 손상되면 메모리가 해제됩니다 window.f_.

제 생각에는 이것은 버그가 아닙니다.


IE9 +와 Firefox에서 이것을 테스트했습니다.

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);

여기에 라이브 사이트가 있습니다 .

나는 function() {}최소한의 메모리를 사용하여 500의 배열로 마무리하고 싶었습니다 .

Unfortunately, that was not the case. Each empty function holds on to an (forever unreachable, but not GC'ed) array of a million numbers.

Chrome eventually halts and dies, Firefox finishes the whole thing after using nearly 4GB of RAM, and IE grows asymptotically slower until it shows "Out of memory".

Removing either one of the commented lines fixes everything.

It seems that all three of these browsers (Chrome, Firefox, and IE) keep an environment record per context, not per closure. Boris hypothesizes the reason behind this decision is performance, and that seems likely, though I'm not sure how performant it can be called in light of the above experiment.

If a need a closure referencing some (granted I didn't use it here, but imagine I did), if instead of

function g() { some; }

I use

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

it will fix the memory problems by moving the closure to a different context than my other function.

This will make my life much more tedious.

P.S. Out of curiousity, I tried this in Java (using its ability to define classes inside of functions). GC works as I had originally hoped for Javascript.


Heuristics vary, but a common way to implement this sort of thing is to create an environment record for each call to f() in your case, and only store the locals of f that are actually closed over (by some closure) in that environment record. Then any closure created in the call to f keeps alive the environment record. I believe this is how Firefox implements closures, at least.

This has the benefits of fast access to closed-over variables and simplicity of implementation. It has the drawback of the observed effect, where a short-lived closure closing over some variable causes it to be kept alive by long-lived closures.

One could try creating multiple environment records for different closures, depending on what they actually close over, but that can get very complicated very quickly and can cause performance and memory problems of its own...


  1. Maintain State between function calls Let’s say you have function add() and you would like it to add all the values passed to it in several calls and return the sum.

like add(5); // returns 5

add(20); // returns 25 (5+20)

add(3); // returns 28 (25 + 3)

두 가지 방법으로 먼저 전역 변수를 정의 할 수 있습니다. 물론 총계를 유지하기 위해 전역 변수를 사용할 수 있습니다. 그러나 당신이 (ab) 글로벌을 사용한다면이 친구는 당신을 살아있게 먹을 것입니다.

전역 변수를 정의하지 않고 클로저사용하는 최신 방법

(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));
  
}());


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


(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
    
  
}());

참고 URL : https://stackoverflow.com/questions/19798803/how-javascript-closures-are-garbage-collected

반응형