.NET में कचरा संग्रहण को समझना


170

नीचे दिए गए कोड पर विचार करें:

public class Class1
{
    public static int c;
    ~Class1()
    {
        c++;
    }
}

public class Class2
{
    public static void Main()
    {
        {
            var c1=new Class1();
            //c1=null; // If this line is not commented out, at the Console.WriteLine call, it prints 1.
        }
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine(Class1.c); // prints 0
        Console.Read();
    }
}

अब, भले ही मुख्य विधि में चर c1 दायरे से बाहर है और किसी अन्य वस्तु द्वारा आगे संदर्भित नहीं है जब GC.Collect()कहा जाता है, तो इसे अंतिम रूप क्यों नहीं दिया जाता है?


8
जब वे कार्यक्षेत्र से बाहर हो जाते हैं, तो GC तुरंत मुक्त उदाहरण नहीं देता है। ऐसा तब होता है जब यह सोचता है कि यह आवश्यक है। आप यहां GC के बारे में सब कुछ पढ़ सकते हैं: msdn.microsoft.com/en-US/library/vstudio/0xy59wtx.aspx
user1908061

@ user1908061 (Psstst। आपका लिंक टूट गया है।)
ड्रैगोकॉक

जवाबों:


352

आपको यहाँ फँसाया जा रहा है और बहुत गलत निष्कर्ष आ रहे हैं क्योंकि आप डिबगर का उपयोग कर रहे हैं। आपको अपना कोड अपने उपयोगकर्ता की मशीन पर चलने के तरीके को चलाने की आवश्यकता होगी। बिल्ड + कॉन्फ़िगरेशन प्रबंधक के साथ रिलीज़ बिल्ड पर स्विच करें, ऊपरी बाएँ कोने में "सक्रिय समाधान कॉन्फ़िगरेशन" कॉम्बो को "रिलीज़" में बदलें। इसके बाद, टूल + ऑप्शन में जाएं, डीबगिंग, जनरल और "सप्रेस जेआईटी ऑप्टिमाइज़ेशन" विकल्प को अनटिक करें।

अब अपने प्रोग्राम को फिर से चलाएं और सोर्स कोड के साथ टिंकर करें। ध्यान दें कि अतिरिक्त ब्रेसिज़ का कोई प्रभाव नहीं पड़ता है। और ध्यान दें कि चर को शून्य पर सेट करने से कोई अंतर नहीं पड़ता है। यह हमेशा "1" प्रिंट करेगा। यह अब आप जिस तरह से उम्मीद करते हैं और उम्मीद है कि यह काम करेगा।

जब आप डिबग बिल्ड चलाते हैं तो यह अलग-अलग तरीके से क्यों काम करता है, यह समझाने का काम छोड़ देता है। यह समझाने की आवश्यकता है कि कचरा संग्रहकर्ता स्थानीय चर को कैसे बचाता है और वर्तमान डिबगर होने से कैसे प्रभावित होता है।

सबसे पहले, जब मशीन विधि में IL के लिए संकलित करता है तो घबराना दो महत्वपूर्ण कर्तव्य करता है। पहले वाला डीबगर में बहुत अधिक दिखाई देता है, आप मशीन कोड को डिबग + विंडोज + डिस्सेम्फ़र विंडो के साथ देख सकते हैं। दूसरा कर्तव्य हालांकि पूरी तरह से अदृश्य है। यह एक तालिका भी बनाता है जो बताता है कि विधि निकाय के अंदर स्थानीय चर का उपयोग कैसे किया जाता है। उस तालिका में प्रत्येक विधि तर्क और दो पतों के साथ स्थानीय चर के लिए एक प्रविष्टि है। वह पता जहां चर पहले ऑब्जेक्ट संदर्भ को संग्रहीत करेगा। और मशीन कोड निर्देश का पता जहां उस चर का उपयोग नहीं किया जाता है। यह भी कि क्या वैरिएबल स्टैक फ्रेम या एक सीपीयू रजिस्टर पर संग्रहीत है।

यह तालिका कचरा संग्रहकर्ता के लिए आवश्यक है, यह जानने की जरूरत है कि संग्रह करते समय वस्तु संदर्भों को कहां देखना है। जब संदर्भ GC ढेर पर किसी वस्तु का हिस्सा हो तो करना बहुत आसान है। सीपीयू रजिस्टर में ऑब्जेक्ट रेफरेंस स्टोर किए जाने पर निश्चित रूप से करना आसान नहीं है। मेज कहती है कि कहां देखना है।

तालिका में "अब उपयोग नहीं किया गया" पता बहुत महत्वपूर्ण है। यह कचरा संग्रहकर्ता को बहुत कुशल बनाता है । यह एक ऑब्जेक्ट संदर्भ एकत्र कर सकता है, भले ही इसका उपयोग किसी विधि के अंदर किया गया हो और उस विधि ने अभी तक निष्पादित नहीं किया है। जो बहुत सामान्य है, उदाहरण के लिए आपका Main () तरीका कभी भी आपके प्रोग्राम के समाप्त होने से ठीक पहले निष्पादित करना बंद कर देगा। स्पष्ट रूप से आप कार्यक्रम के अवधि के लिए उस मुख्य () पद्धति के अंदर उपयोग की जाने वाली कोई भी वस्तु संदर्भ नहीं चाहते हैं, जो एक रिसाव की मात्रा होगी। घबराना यह पता लगाने के लिए तालिका का उपयोग कर सकता है कि इस तरह का एक स्थानीय चर अब उपयोगी नहीं है, यह इस बात पर निर्भर करता है कि कॉल करने से पहले कार्यक्रम उस मेन () विधि के अंदर कितना आगे बढ़ गया है।

लगभग एक जादुई विधि जो उस तालिका से संबंधित है GC.KeepAlive () है। यह एक बहुत ही खास तरीका है, यह कोई भी कोड उत्पन्न नहीं करता है। इसका एकमात्र कर्तव्य उस तालिका को संशोधित करना है। इसका विस्तार होता हैस्थानीय चर का जीवनकाल, संदर्भ को संग्रहित करने से रोकती है ताकि कचरा एकत्र किया जा सके। केवल समय का उपयोग करने के लिए आपको जीसी को रोकने के लिए एक संदर्भ एकत्र करने के साथ अति-उत्सुक होने से रोकना है, यह इंटरोप परिदृश्यों में हो सकता है जहां एक संदर्भ अप्रबंधित कोड को पारित किया जाता है। कचरा संग्रहकर्ता ऐसे संदर्भों का उपयोग ऐसे कोड द्वारा नहीं देख सकता है क्योंकि यह घबराना द्वारा संकलित नहीं किया गया था, इसलिए तालिका नहीं है जो कहती है कि संदर्भ के लिए कहां देखें। जब आप GC.KeepAlive () का उपयोग करने की आवश्यकता होती है, तो EnumWindows () की तरह एक अप्रबंधित फ़ंक्शन के लिए एक प्रतिनिधि ऑब्जेक्ट को पास करना बॉयलरप्लेट उदाहरण है।

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

यह अब यह भी बताता है कि आपने पहले क्या देखा था और आपने सवाल क्यों पूछा। यह "0" प्रिंट करता है क्योंकि GC.Collect कॉल संदर्भ एकत्र नहीं कर सकता है। तालिका का कहना है कि चर GC.Collect () कॉल के अतीत के उपयोग में है , सभी विधि के अंत तक। डिबगर संलग्न होने और डीबग बिल्ड चलाकर ऐसा कहने के लिए मजबूर किया गया ।

चर को अशक्त करने के लिए अब एक प्रभाव पड़ता है क्योंकि जीसी चर का निरीक्षण करेगा और अब एक संदर्भ नहीं देखेगा। लेकिन यह सुनिश्चित करें कि आप उस जाल में न पड़ें, जिसमें कई C # प्रोग्रामर गिर गए हों, वास्तव में उस कोड को लिखना व्यर्थ था। इससे कोई फ़र्क नहीं पड़ता कि जब आप रिलीज़ बिल्ड में कोड चलाते हैं तो वह कथन मौजूद है या नहीं। वास्तव में, घबराने वाला आशावादी उस बयान को हटा देगा क्योंकि इसका कोई प्रभाव नहीं है। इसलिए कोड को उस तरह से नहीं लिखना सुनिश्चित करें, भले ही इसका असर दिख रहा हो।


इस विषय के बारे में एक अंतिम ध्यान दें, यह वही है जो प्रोग्रामर को परेशानी में डालती है जो एक कार्यालय ऐप के साथ कुछ करने के लिए छोटे प्रोग्राम लिखते हैं। डिबगर आमतौर पर उन्हें गलत रास्ते पर ले जाता है, वे चाहते हैं कि कार्यालय कार्यक्रम मांग पर बाहर निकले। उचित तरीका यह है कि GC.Collect () कॉल करके। लेकिन उन्हें पता चलेगा कि जब वे अपने ऐप को डिबग करते हैं, तो यह काम नहीं करता है, जिससे उन्हें कभी-कभी मार्शल-कॉल-बीकॉम.कॉम () द्वारा भूमि नहीं मिलती है। मैनुअल मेमोरी प्रबंधन, यह शायद ही कभी ठीक से काम करता है क्योंकि वे आसानी से एक अदृश्य इंटरफ़ेस संदर्भ को अनदेखा कर देंगे। GC.Collect () वास्तव में काम करता है, जब आप ऐप को डिबग नहीं करते हैं।


1
मेरा प्रश्न भी देखिए कि हंस ने मेरे लिए उत्तर दिया। stackoverflow.com/questions/15561025/…
डेव नै

1
@ हंसपसंद मुझे बस यह भयानक स्पष्टीकरण मिला, जो यहां मेरे सवाल का हिस्सा भी है: जीसी और थ्रेड सिंक्रोनाइज़ेशन के बारे में stackoverflow.com/questions/30529379/… । एक प्रश्न जो मेरे पास अभी भी है: मैं सोच रहा हूं कि क्या जीसी वास्तव में कॉम्पैक्ट और अपडेट पते हैं जो एक रजिस्टर में उपयोग किए जाते हैं (निलंबित करते समय मेमोरी में संग्रहीत), या बस उन्हें छोड़ देता है? एक प्रक्रिया जो थ्रेड को निलंबित करने के बाद रजिस्टर को अपडेट कर रही है (फिर से शुरू होने से पहले) मुझे लगता है कि ओएस द्वारा अवरुद्ध एक गंभीर सुरक्षा धागे की तरह है।
15'15 बजे atlaste

अप्रत्यक्ष रूप से, हाँ। थ्रेड निलंबित है, जीसी सीपीयू रजिस्टरों के लिए बैकिंग स्टोर को अपडेट करता है। एक बार जब थ्रेड चलना शुरू हो जाता है, तो यह अब अपडेटेड रजिस्टर वैल्यू का उपयोग करता है।
हंस पसेंट

1
@ हंसपसंद, अगर आप यहां वर्णित सीएलआर कचरा कलेक्टर के कुछ गैर-स्पष्ट विवरणों के संदर्भ जोड़ते हैं, तो मैं सराहना करूंगा।
०१ पर denfromufa

ऐसा लगता है कि कॉन्फ़िगरेशन बुद्धिमान है, एक महत्वपूर्ण बिंदु यह है कि "ऑप्टिमाइज़ कोड" ( <Optimize>true</Optimize>इन .csproj) सक्षम है। यह "रिलीज़" कॉन्फ़िगरेशन में डिफ़ॉल्ट है। लेकिन अगर कोई कस्टम कॉन्फ़िगरेशन का उपयोग करता है, तो यह जानना प्रासंगिक है कि यह सेटिंग महत्वपूर्ण है।
शून्य

34

[बस अंतिम प्रक्रिया के आंतरिक मामलों में आगे जोड़ना चाहता था]

तो, आप एक ऑब्जेक्ट बनाते हैं और जब ऑब्जेक्ट एकत्र किया जाता है, तो ऑब्जेक्ट की Finalizeविधि को बुलाया जाना चाहिए। लेकिन इस बहुत ही सरल धारणा की तुलना में अंतिम रूप देना अधिक है।

लघु अवधारणाओं ::

  1. ऑब्जेक्ट्स Finalizeविधियों को लागू नहीं कर रहे हैं, वहां मेमोरी तुरंत पुनर्प्राप्त की जाती है, जब तक कि निश्चित रूप से, वे
    अब एप्लिकेशन कोड द्वारा पहुंच योग्य नहीं हैं

  2. को लागू करने ऑब्जेक्ट्स Finalizeविधि, संकल्पना / का कार्यान्वयन Application Roots, Finalization Queue, Freacheable Queueइससे पहले कि वे पुन: दावा किया जा सकता है आता है।

  3. किसी भी वस्तु को कचरा माना जाता है यदि यह अनुप्रयोग कोड द्वारा पहुंच योग्य नहीं है

मान लें: क्लास / ऑब्जेक्ट्स ए, बी, डी, जी, एच लागू नहीं है Finalizeविधि और सी, ई, एफ, आई, जे लागू Finalizeविधि।

जब कोई एप्लिकेशन एक नया ऑब्जेक्ट बनाता है, तो नया ऑपरेटर मेमोरी को ढेर से आवंटित करता है। यदि ऑब्जेक्ट के प्रकार में एक Finalizeविधि है, तो ऑब्जेक्ट के लिए एक पॉइंटर को अंतिम रूप देने वाली कतार पर रखा जाता है

इसलिए C, E, F, I, J की वस्तुओं की ओर इशारा करता है, इसे अंतिम रूप देने के लिए जोड़ा जाता है। अंतिम रूप दिए जाने के लिए कतार में एक आंतरिक डेटा कचरा कलेक्टर द्वारा नियंत्रित संरचना है। कतार में प्रत्येक प्रविष्टि किसी वस्तु की ओर इशारा करती है जो कि वस्तु की मेमोरी के पुनः प्राप्त होने से पहले उसकी विधि होनी चाहिए । नीचे चित्र कई वस्तुओं वाले ढेर को दर्शाता है। इनमें से कुछ ऑब्जेक्ट एप्लिकेशन की जड़ों से पहुंच योग्य हैं

Finalize, और कुछ नहीं हैं। जब C, E, F, I और J को ऑब्जेक्ट बनाया जाता है, तो .नेट फ्रेमवर्क यह पता लगाता है कि इन ऑब्जेक्ट्स के Finalizeतरीके हैं और इन ऑब्जेक्ट्स के पॉइंटर्स को अंतिम रूप देने वाली कतार में जोड़ा जाता है ।

यहां छवि विवरण दर्ज करें

जब कोई GC (प्रथम संग्रह) होता है, तो वस्तुएँ B, E, G, H, I और J कचरा होने के लिए निर्धारित होती हैं। क्योंकि ए, सी, डी, एफ अभी भी ऊपर पीले बॉक्स से तीर के माध्यम से दर्शाए गए एप्लिकेशन कोड द्वारा पहुंच योग्य हैं।

कचरा संग्रहकर्ता इन वस्तुओं की ओर संकेत करने वाली अंतिम कतार को स्कैन करता है । जब एक पॉइंटर मिलता है, तो पॉइंटर को अंतिम रूप से कतार से हटा दिया जाता है और उसे दूर की कतार ("एफ- रीचेबल ") में जोड़ दिया जाता हैFreachable कतार एक और आंतरिक डेटा कचरा कलेक्टर द्वारा नियंत्रित संरचना है। भयावह कतार में प्रत्येक सूचक एक ऐसी वस्तु की पहचान करता है, जो अपनी विधि कहलाने के लिए तैयार है ।

Finalize

संग्रह (प्रथम संग्रह) के बाद, प्रबंधित ढेर नीचे की आकृति के समान दिखता है। नीचे दिए गए स्पष्टीकरण ::
1.) वस्तुओं बी, जी और एच द्वारा कब्जा की गई स्मृति को तुरंत पुनः प्राप्त किया गया है क्योंकि इन वस्तुओं में अंतिम विधि नहीं थी जिसे कहा जाना चाहिए

2.) हालाँकि, ऑब्जेक्ट E, I, और J द्वारा कब्जा की गई मेमोरी को दोबारा प्राप्त नहीं किया जा सकता क्योंकि उनकी Finalizeविधि को अभी तक नहीं बुलाया गया है। अंतिम विधि को कॉल करने से दूर की कतार द्वारा किया जाता है

3.) ए, सी, डी, एफ अभी भी पीले बॉक्स से ऊपर तीर के माध्यम से दर्शाए गए एप्लिकेशन कोड द्वारा पहुंच योग्य हैं, इसलिए उन्हें किसी भी मामले में एकत्र नहीं किया जाएगा।

यहां छवि विवरण दर्ज करें

अंतिम रूप देने के तरीकों के लिए समर्पित एक विशेष रनटाइम थ्रेड है। जब अगम्य कतार खाली होती है (जो आमतौर पर मामला है), यह धागा सोता है। लेकिन जब प्रविष्टियां दिखाई देती हैं, तो यह थ्रेड जागता है, कतार से प्रत्येक प्रविष्टि को हटाता है, और प्रत्येक ऑब्जेक्ट की अंतिम विधि को कॉल करता है। कचरा संग्रहकर्ता पुनः प्राप्त करने योग्य मेमोरी को संकुचित करता है और विशेष रनटाइम थ्रेड प्रत्येक ऑब्जेक्ट की Finalizeविधि को निष्पादित करते हुए, दूर की कतार को खाली करता है । तो यहाँ अंत में है जब आपका अंतिम तरीका निष्पादित हो जाता है

अगली बार कचरा संग्रहकर्ता को (2 डी संग्रह) लगाया जाता है, यह देखता है कि अंतिम रूप से रखी गई वस्तुएं वास्तव में कचरा हैं, क्योंकि आवेदन की जड़ें इसे इंगित नहीं करती हैं और अगम्य कतार अब इसे इंगित नहीं करती है (यह भी बहुत अच्छा है), इसलिए ऑब्जेक्ट्स (E, I, J) के लिए मेमोरी को केवल Heap.See फिगर के नीचे से रिकवर किया गया है और इसे ऊपर दिए गए फिगर से तुलना करें

यहां छवि विवरण दर्ज करें

यहां समझने वाली महत्वपूर्ण बात यह है कि अंतिम रूप से आवश्यक वस्तुओं द्वारा उपयोग की जाने वाली स्मृति को पुनः प्राप्त करने के लिए दो जीसी की आवश्यकता होती है । वास्तव में, दो से अधिक संग्रह टैक्सी की आवश्यकता होती है क्योंकि इन वस्तुओं को एक पुरानी पीढ़ी को बढ़ावा मिल सकता है

नोट :: freachable कतार एक रूट वैश्विक और स्थैतिक चर की तरह जड़ें हैं माना जाता है। इसलिए, यदि कोई वस्तु विचारणीय कतार पर है, तो वह वस्तु उपलब्ध नहीं है और कचरा नहीं है।

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

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