जावास्क्रिप्ट क्लोजर कचरा कैसे एकत्र किया जाता है


168

मैंने निम्नलिखित क्रोम बग को लॉग किया है , जिसके कारण मेरे कोड में कई गंभीर और गैर-स्पष्ट मेमोरी लीक हो गए हैं:

(ये परिणाम क्रोम देव टूल्स के मेमोरी प्रोफाइलर का उपयोग करते हैं , जो जीसी चलाता है, और फिर सब कुछ एकत्र नहीं किया जाता है।

नीचे दिए गए कोड में, 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+ और फ़ायरफ़ॉक्स) में बंद होने के कचरा संग्रह के बारे में है। मैं वेबकिट के टूल से काफी परिचित हूं, जैसे कि जावास्क्रिप्ट हीप प्रोफाइलर, लेकिन मैं अन्य ब्राउज़रों के टूल के बारे में बहुत कम जानता हूं, इसलिए मैं इसका परीक्षण नहीं कर पाया।

IE9 + और फ़ायरफ़ॉक्स कचरा इन तीनों में से किस मामले में उदाहरण एकत्र करेगा someClass ?


4
Uninitiated के लिए, Chrome आपको कैसे परीक्षण करने देता है कि कौन से चर / ऑब्जेक्ट कचरा एकत्र किए जाते हैं, और जब ऐसा होता है?
nnnnnn 20

1
हो सकता है कि कंसोल इसके लिए एक संदर्भ रख रहा हो। जब आप कंसोल को साफ़ करते हैं तो क्या यह GCed हो जाता है?
डेविड

1
@david अंतिम उदाहरण में unreachableफ़ंक्शन को कभी निष्पादित नहीं किया जाता है, इसलिए वास्तव में कुछ भी लॉग नहीं किया जाता है।
जेम्स मॉन्टेन

1
मुझे यह विश्वास करने में परेशानी हो रही है कि उस महत्व का एक बग गुजर गया, भले ही हमें तथ्यों का सामना करना पड़े। हालाँकि मैं बार-बार कोड देख रहा हूँ और मुझे कोई अन्य व्याख्या नहीं मिल रही है। आपने कंसोल में कोड को सही से नहीं चलाने की कोशिश की (उर्फ ब्राउज़र को लोडेड स्क्रिप्ट से स्वाभाविक रूप से चलाने दें)?
प्लेक्सएक्स

1
@ कुछ समय पहले, मैंने वह लेख पढ़ा है। इसे "जावास्क्रिप्ट अनुप्रयोगों में परिपत्र संदर्भों को संभालना" सबटाइटल किया गया है, लेकिन जेएस / डीओएम परिपत्र संदर्भों की चिंता कोई आधुनिक ब्राउज़र पर लागू नहीं होती है। इसमें बंदियों का उल्लेख है, लेकिन सभी उदाहरणों में, प्रश्न में चर अभी भी कार्यक्रम द्वारा संभव उपयोग में थे।
पॉल ड्रेपर

जवाबों:


78

जहां तक ​​मैं बता सकता हूं, यह बग नहीं बल्कि अपेक्षित व्यवहार है।

मोज़िला के मेमोरी प्रबंधन पृष्ठ से : "2012 तक, सभी आधुनिक ब्राउज़र एक मार्क-एंड-स्वीप कचरा-कलेक्टर जहाज करते हैं।" "सीमा: वस्तुओं को स्पष्ट रूप से पहुंच से बाहर बनाने की आवश्यकता है "

आपके उदाहरणों में जहां यह विफल रहता है someवह अभी भी क्लोजर में उपलब्ध है। मैंने इसे अप्राप्य बनाने के लिए दो तरीके आजमाए और दोनों काम किए। या तो आप सेट some=nullकरते हैं जब आपको इसकी आवश्यकता नहीं होती है, या आप सेट करते हैं window.f_ = null;और यह चला जाएगा।

अपडेट करें

मैंने इसे विंडोज पर क्रोम 30, एफएफ 25, ओपेरा 12 और आईई 10 में आजमाया है।

मानक कचरा संग्रहण के बारे में कुछ नहीं कहा है, लेकिन क्या होना चाहिए की कुछ सुराग देता है।

  • धारा 13 फ़ंक्शन की परिभाषा, चरण 4: "13.2 में निर्दिष्ट नई फ़ंक्शन ऑब्जेक्ट बनाने का परिणाम बंद करें"
  • धारा 13.2 "स्कोप द्वारा निर्दिष्ट एक लेक्सिकल पर्यावरण" (गुंजाइश = बंद)
  • धारा 10.2 लेक्सिकल वातावरण:

"(आंतरिक) लेक्सिकल एनवायरनमेंट का बाहरी संदर्भ लेक्सिकल एनवायरनमेंट का एक संदर्भ है जो तार्किक रूप से इनर लेक्सिकल एनवायरनमेंट को घेरता है।

एक बाहरी लेक्सिकल पर्यावरण, ज़ाहिर है, अपने स्वयं के बाहरी लेक्सिकल पर्यावरण हो सकता है। एक लेक्सिकल एनवायरनमेंट कई इनर लेक्सिकल एनवायरनमेंट के लिए बाहरी वातावरण के रूप में काम कर सकता है। उदाहरण के लिए, यदि किसी फंक्शन डिक्लेरेशन में दो नेस्टेड फंक्शन डिक्लेरेशन होते हैं , तो नेस्टेड फंक्शंस में से प्रत्येक के लेक्सिकल एनवायरनमेंट में उनके बाहरी लेक्सिकल एनवायरनमेंट के आसपास के फंक्शन के मौजूदा एक्जिक्यूटिव के लेक्सिकल एनवायरनमेंट होंगे। "

तो, एक फ़ंक्शन के पास अभिभावक के पर्यावरण तक पहुंच होगी।

इसलिए, someरिटर्निंग फ़ंक्शन को बंद करने के लिए उपलब्ध होना चाहिए।

फिर यह हमेशा उपलब्ध क्यों नहीं है?

ऐसा लगता है कि क्रोम और एफएफ कुछ मामलों में वेरिएबल को खत्म करने के लिए काफी स्मार्ट है, लेकिन ओपेरा और आईई दोनों में someवैरिएबल क्लोजर में उपलब्ध है (एनबी: इस सेट को देखने के लिए return nullऔर डिबगर की जांच करें)।

जीसी का पता लगाने के लिए सुधार किया जा सकता है someकि क्या कार्यों में उपयोग किया जाता है या नहीं, लेकिन यह जटिल होगा।

एक बुरा उदाहरण:

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_

मेरी राय में यह बग नहीं है।


4
लेकिन, एक बार setTimeout()कॉलबैक चलने के बाद, कॉलबैक का फंक्शन स्कोप हो setTimeout()जाता है और इसके संदर्भ को जारी करते हुए पूरे स्कोप को कचरा एकत्र किया जाना चाहिए some। अब कोई कोड नहीं है जो चल सकता someहै जो बंद होने की स्थिति में पहुंच सकता है । इसे कचरा एकत्र किया जाना चाहिए। अंतिम उदाहरण और भी बदतर है क्योंकि unreachable()इसे भी नहीं बुलाया गया है और किसी के पास इसका संदर्भ नहीं है। इसका दायरा GCed भी होना चाहिए। ये दोनों बग की तरह लग रहे हैं। फ़ंक्शन दायरे में "मुफ्त" चीजों के लिए जेएस में कोई भाषा की आवश्यकता नहीं है।
jfriend00

1
@ कुछ ऐसा नहीं होना चाहिए। फ़ंक्शन वे वैरिएबल को बंद करने के लिए नहीं हैं जो वे आंतरिक रूप से उपयोग नहीं कर रहे हैं।
plxx

2
इसे खाली फ़ंक्शन द्वारा एक्सेस किया जा सकता है, लेकिन ऐसा नहीं है कि इसके लिए कोई वास्तविक संदर्भ नहीं हैं इसलिए यह स्पष्ट होना चाहिए। कचरा संग्रह वास्तविक संदर्भों पर नज़र रखता है। यह सब कुछ है कि संदर्भित किया जा सकता है पर पकड़ नहीं है, केवल चीजें हैं जो वास्तव में संदर्भित हैं। एक बार अंतिम f()कहे जाने के बाद, someकिसी भी अधिक वास्तविक संदर्भ नहीं हैं । यह अगम्य है और जीसीड होना चाहिए।
jfriend00

1
@ jfriend00 मुझे कुछ भी नहीं मिल रहा है (मानक) [ ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf] केवल उन चरों के बारे में कुछ भी कहता है जो इसे आंतरिक रूप से उपलब्ध होने चाहिए। धारा 13 में, उत्पादन चरण 4: बंद करने का परिणाम 13.2 , 10.2 में निर्दिष्ट के रूप में एक नया फ़ंक्शन ऑब्जेक्ट बनाने का परिणाम है । बाहरी पर्यावरण संदर्भ को लेक्सिकल पर्यावरण मानों के तार्किक नेस्टिंग मॉडल के लिए उपयोग किया जाता है। (आंतरिक का बाहरी संदर्भ ) लेक्सिकल एनवायरनमेंट लेक्सिकल एनवायरनमेंट का एक संदर्भ है जो तार्किक रूप से आंतरिक लेक्सिकल एनवायरनमेंट को घेरता है। "
कुछ

2
खैर, evalवास्तव में विशेष मामला है। उदाहरण के लिए, evalउपनाम नहीं किया जा सकता ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ), उदा var eval2 = eval। यदि evalइसका उपयोग किया जाता है (और चूंकि इसे अलग नाम से नहीं बुलाया जा सकता है, तो यह करना आसान है), तो हमें यह मान लेना चाहिए कि यह किसी भी चीज का उपयोग कर सकता है।
पॉल ड्रेपर

49

मैंने IE9 + और फ़ायरफ़ॉक्स में इसका परीक्षण किया।

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'ed) एरे पर रखता है।

क्रोम अंततः रुक जाता है और मर जाता है, फ़ायरफ़ॉक्स लगभग 4 जीबी रैम का उपयोग करने के बाद पूरी तरह से खत्म कर देता है, और आईई जब तक यह "मेमोरी से बाहर" नहीं दिखाती तब तक यह धीमी गति से बढ़ता है।

कमेंट की गई लाइनों में से किसी एक को हटाने से सब कुछ ठीक हो जाता है।

ऐसा लगता है कि इन तीनों ब्राउज़रों (क्रोम, फ़ायरफ़ॉक्स, और आईई) के संदर्भ में एक पर्यावरण रिकॉर्ड रखा गया है, न कि प्रति बंद। इस निर्णय के पीछे बोरिस परिकल्पना का कारण प्रदर्शन है, और यह संभावना प्रतीत होती है, हालांकि मुझे यकीन नहीं है कि उपर्युक्त प्रयोग के प्रकाश में इसे कैसे प्रदर्शन कहा जा सकता है।

यदि एक बंद संदर्भ की जरूरत है some(दी मैं इसे यहाँ का उपयोग नहीं किया, लेकिन कल्पना मैंने किया था), अगर के बजाय

function g() { some; }

मैं उपयोग करता हूं

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

यह मेरे अन्य फ़ंक्शन की तुलना में मेमोरी को समस्याओं को एक अलग संदर्भ में स्थानांतरित करके ठीक कर देगा।

यह मेरे जीवन को और अधिक थकाऊ बना देगा।

PS जिज्ञासा से बाहर, मैंने जावा में यह कोशिश की (कार्यों के अंदर कक्षाओं को परिभाषित करने की अपनी क्षमता का उपयोग करके)। जीसी काम करता है जैसा कि मैंने मूल रूप से जावास्क्रिप्ट के लिए आशा की थी।


मुझे लगता है कि बाहरी फंक्शन var g = (फंक्शन (कुछ) {रिटर्न फंक्शन () {कुछ;}}} (कुछ) के लिए बंद कोष्ठक छूट गया;
एचसीजे

15

आंकड़े अलग-अलग होते हैं, लेकिन इस तरह की बात को लागू करने का एक सामान्य तरीका यह है कि f()आपके मामले में प्रत्येक कॉल के लिए एक पर्यावरण रिकॉर्ड बनाया जाए , और केवल उस स्थानीय रिकॉर्ड को उस वातावरण रिकॉर्ड में बंद fकर दिया जाए ( कुछ बंद करके )। फिर fपर्यावरण रिकॉर्ड को जीवित रखने के लिए कॉल में निर्मित कोई भी बंद । मेरा मानना ​​है कि कम से कम फ़ायरफ़ॉक्स कैसे लागू होता है।

इससे क्लोज-ओवर वैरिएबल तक तेजी से पहुंच और कार्यान्वयन की सादगी के लाभ हैं। इसमें देखे गए प्रभाव का दोष है, जहां कुछ चर पर एक अल्पकालिक बंद होने का कारण यह लंबे समय तक रहने वाले बंदों द्वारा जीवित रखा जाता है।

एक व्यक्ति अलग-अलग क्लोजर के लिए कई पर्यावरण रिकॉर्ड बनाने की कोशिश कर सकता है, जो इस बात पर निर्भर करता है कि वे वास्तव में क्या बंद करते हैं, लेकिन यह बहुत जल्दी जटिल हो सकता है और इसके प्रदर्शन और स्मृति समस्याओं का कारण बन सकता है ...


आपके निरीक्षण के लिए धन्यवाद। मैं यह निष्कर्ष निकालने के लिए आया हूं कि यह भी है कि क्रोम कैसे लागू होता है। मैंने हमेशा सोचा था कि उन्हें बाद के तरीके से लागू किया गया था, जिसमें प्रत्येक बंद को केवल उस वातावरण को रखा गया था जिसकी उसे आवश्यकता थी, लेकिन ऐसा नहीं है। मुझे आश्चर्य है कि क्या यह वास्तव में कई पर्यावरण रिकॉर्ड बनाने के लिए जटिल है। क्लोजर के संदर्भों को एकत्र करने के बजाय, ऐसा कार्य करें जैसे कि प्रत्येक एक ही क्लोजर हो। मैंने अनुमान लगाया था कि प्रदर्शन के विचार यहाँ तर्क थे, हालांकि मेरे लिए साझा पर्यावरण रिकॉर्ड होने के परिणाम और भी बुरे लगते हैं।
पॉल ड्रेपर

कुछ मामलों में बाद का तरीका पर्यावरण रिकॉर्ड की संख्या में एक विस्फोट की ओर जाता है जिसे बनाने की आवश्यकता है। जब तक आप उन्हें जब आप कर सकते हैं कार्यों में साझा करने के लिए कड़ी मेहनत करते हैं, लेकिन तब आपको ऐसा करने के लिए जटिल मशीनरी का एक गुच्छा चाहिए। यह संभव है, लेकिन मुझे बताया गया है कि प्रदर्शनकारी व्यापार मौजूदा दृष्टिकोण के पक्ष में हैं।
बोरिस ज़बर्स्की

रिकॉर्ड की संख्या बनाई गई क्लोजर की संख्या के बराबर है। मैं एक विस्फोट के रूप में वर्णित O(n^2)कर सकता हूं O(2^n), लेकिन आनुपातिक वृद्धि नहीं।
पॉल ड्रेपर

ठीक है, ओ (एन) ओ (1) की तुलना में एक विस्फोट है, खासकर जब प्रत्येक एक उचित मात्रा में स्मृति ले सकता है ... फिर, मैं इस पर एक विशेषज्ञ नहीं हूं; irc.mozilla.org पर #jsapi चैनल पर पूछने पर आपको ट्रेडऑफ़ क्या है, इससे बेहतर और विस्तृत विवरण मिल सकता है।
बोरिस ज़बर्स्की

1
@Esailija यह वास्तव में बहुत आम है, दुर्भाग्य से। आप सभी की जरूरत समारोह में एक बड़ा अस्थायी है (आमतौर पर एक बड़े टाइप सरणी) कि कुछ यादृच्छिक अल्पकालिक कॉलबैक उपयोग और एक लंबे समय से रहने वाले बंद। यह हाल ही में वेब ऐप लिखने वाले लोगों के लिए कई बार आया है ...
बोरिस ज़बर्स्की

0
  1. फ़ंक्शन कॉल के बीच स्थिति बनाए रखें मान लीजिए कि आपके पास फ़ंक्शन ऐड () है और आप इसे कई कॉल में दिए गए सभी मानों को जोड़ना चाहते हैं और राशि लौटा देंगे।

जैसे जोड़ें (5); // रिटर्न 5

(20) को जोड़ने; // रिटर्न 25 (5 + 20)

(3) जोड़ने; // रिटर्न 28 (25 + 3)

दो तरीकों से आप यह कर सकते हैं पहला सामान्य रूप से एक वैश्विक चर को परिभाषित करता है , कुल को पकड़ने के लिए आप एक वैश्विक चर का उपयोग कर सकते हैं। लेकिन ध्यान रखें कि यदि आप (अब) ग्लोबल्स का उपयोग करते हैं तो यह दोस्त आपको जीवित खा जाएगा।

अब वैश्विक चर को परिभाषित करने के साथ बंद करने का उपयोग करने का नवीनतम तरीका

(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

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


कृपया उत्तर का वर्णन करें
janith1024

0

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.