[javascript] 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의 배열로 마무리하고 싶었습니다 .

불행히도, 그렇지 않았습니다. 각 빈 함수는 백만 개의 숫자로 구성된 (영원히 도달 할 수 없지만 GC가 아닌) 배열을 유지합니다.

Chrome은 결국 중단되고 죽고 Firefox는 거의 4GB의 RAM을 사용한 후 모든 것을 끝내고 IE는 “메모리 부족”을 표시 할 때까지 점진적으로 느려집니다.

주석 처리 된 행 중 하나를 제거하면 모든 것이 수정됩니다.

이 세 가지 브라우저 (Chrome, Firefox 및 IE)는 모두 폐쇄가 아니라 컨텍스트별로 환경 레코드를 유지하는 것으로 보입니다. Boris는이 결정의 원인이 성능이라는 가설을 세웠으며, 위 실험에 비추어 얼마나 성능이 좋은지 잘 모르겠습니다.

대신 클로저 참조가 필요한 경우 some(여기서 사용하지 않았지만 상상해보십시오)

function g() { some; }

나는 사용한다

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

클로저를 다른 함수와 다른 컨텍스트로 이동하여 메모리 문제를 해결합니다.

이것은 내 인생을 훨씬 더 지루하게 만들 것입니다.

추신 : 호기심으로, Java에서 이것을 시도했습니다 (함수 내부의 클래스를 정의하는 기능 사용). GC는 원래 Javascript를 원했던대로 작동합니다.


답변

휴리스틱은 다양하지만, 이런 종류의 물건을 구현하는 일반적인 방법은 각 통화에 대한 환경 레코드를 생성하는 것입니다 f()귀하의 경우, 단지의 주민들 저장 f실제로 (에 의해 이상 닫혀 일부 해당 환경 레코드에 폐쇄). 그런 다음 호출에서 생성 된 폐쇄 f는 환경 레코드 를 유지합니다. 나는 이것이 Firefox가 최소한 클로저를 구현하는 방법이라고 생각합니다.

이는 닫힌 변수에 빠르게 액세스 할 수 있고 구현이 간편하다는 이점이 있습니다. 일부 변수에 대한 단기 폐쇄 폐쇄로 인해 장기 폐쇄에 의해 생존이 유지되는 관측 된 효과의 단점이 있습니다.

실제로 닫은 내용에 따라 다른 클로저에 대해 여러 환경 레코드를 만들려고 시도 할 수 있지만 매우 복잡해져 자체 성능 및 메모리 문제를 일으킬 수 있습니다.


답변

  1. 함수 호출 사이의 상태 유지 add () 함수가 있고 여러 호출에서 전달 된 모든 값을 추가하고 합계를 반환한다고 가정합니다.

add (5)처럼; // 5를 반환

추가 (20); // 25를 반환합니다 (5 + 20)

추가 (3); // 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


}());


답변