.NET Framework
कचरा इकठा करना
खोज…
परिचय
.Net में, नए () के साथ बनाई गई वस्तुओं को प्रबंधित ढेर पर आवंटित किया जाता है। इन वस्तुओं को कभी भी प्रोग्राम द्वारा अंतिम रूप से उपयोग नहीं किया जाता है जो उनका उपयोग करता है; इसके बजाय, इस प्रक्रिया को .Net कचरा कलेक्टर द्वारा नियंत्रित किया जाता है।
नीचे दिए गए कुछ उदाहरण "प्रयोगशाला मामले" हैं, जिसमें कचरा कलेक्टर को काम पर दिखाया गया है और इसके व्यवहार के कुछ महत्वपूर्ण विवरण हैं, जबकि अन्य इस बात पर ध्यान केंद्रित करते हैं कि गारबेज कलेक्टर द्वारा उचित हैंडलिंग के लिए कक्षाएं कैसे तैयार करें।
टिप्पणियों
गारबेज कलेक्टर को आवंटित स्मृति के संदर्भ में कार्यक्रम की लागत कम करने का लक्ष्य है, लेकिन ऐसा करने में प्रसंस्करण समय के संदर्भ में लागत है। एक अच्छा समग्र समझौता प्राप्त करने के लिए, कई अनुकूलन हैं जिन्हें ध्यान में रखते हुए कूड़ेदान के साथ प्रोग्रामिंग करना चाहिए:
- यदि कलेक्ट () विधि को स्पष्ट रूप से लागू किया जाना है (जो कि वैसे भी अक्सर नहीं होना चाहिए), "अनुकूलित" मोड का उपयोग करने पर विचार करें जो केवल मृत वस्तु को अंतिम रूप देता है जब स्मृति वास्तव में आवश्यक होती है
- संग्रह () विधि को लागू करने के बजाय, AddMemoryPressure () और RemoveMemoryPressure () विधियों का उपयोग करने पर विचार करें, जो केवल स्मृति संग्रह को ट्रिगर करते हैं यदि वास्तव में आवश्यक हो
- सभी मृत वस्तुओं को अंतिम रूप देने के लिए मेमोरी संग्रह की गारंटी नहीं है; इसके बजाय, कचरा संग्रहकर्ता 3 "पीढ़ियों" का प्रबंधन करता है, एक वस्तु कभी-कभी एक पीढ़ी से "जीवित" होती है
- कई थ्रेडिंग मॉडल लागू हो सकते हैं, सेटअप फ़ाइन ट्यूनिंग सहित विभिन्न कारकों पर निर्भर करता है, जिसके परिणामस्वरूप गारबेज कलेक्टर थ्रेड और अन्य एप्लिकेशन थ्रेड (एस) के बीच हस्तक्षेप की विभिन्न डिग्री होती है।
(कचरा) संग्रह का एक मूल उदाहरण
निम्नलिखित वर्ग को देखते हुए:
public class FinalizableObject
{
public FinalizableObject()
{
Console.WriteLine("Instance initialized");
}
~FinalizableObject()
{
Console.WriteLine("Instance finalized");
}
}
एक प्रोग्राम जो बिना उपयोग के भी, एक उदाहरण बनाता है:
new FinalizableObject(); // Object instantiated, ready to be used
निम्नलिखित उत्पादन करता है:
<namespace>.FinalizableObject initialized
यदि कुछ और नहीं होता है, तो कार्यक्रम को समाप्त होने तक ऑब्जेक्ट को अंतिम रूप नहीं दिया जाता है (जो प्रक्रिया में सभी वस्तुओं को मुक्त करता है, इस प्रक्रिया में अंतिम रूप देता है)।
गारबेज कलेक्टर को किसी दिए गए बिंदु पर चलने के लिए मजबूर करना संभव है, इस प्रकार है:
new FinalizableObject(); // Object instantiated, ready to be used
GC.Collect();
जो निम्नलिखित परिणाम उत्पन्न करता है:
<namespace>.FinalizableObject initialized
<namespace>.FinalizableObject finalized
इस बार, जैसे ही गारबेज कलेक्टर को आमंत्रित किया गया, अप्रयुक्त (उर्फ "मृत") वस्तु को अंतिम रूप दिया गया और प्रबंधित हीप से मुक्त किया गया।
जीवित वस्तुओं और मृत वस्तुओं - मूल बातें
अंगूठे का नियम: जब कचरा संग्रह होता है, "जीवित वस्तुएं" वे अभी भी उपयोग में हैं, जबकि "मृत वस्तुएं" अब उपयोग नहीं की जाती हैं (कोई भी चर या क्षेत्र उन्हें संदर्भित करता है, यदि कोई हो, संग्रह से पहले गुंजाइश से बाहर हो गया है) ।
निम्नलिखित उदाहरण में (सुविधा के लिए, FinalizableObject1 और FinalizableObject2 ऊपर दिए गए उदाहरण से FinalizableObject के उपवर्ग हैं और इस प्रकार प्रारंभ / अंतिम संदेश व्यवहार को विरासत में मिलाते हैं):
var obj1 = new FinalizableObject1(); // Finalizable1 instance allocated here
var obj2 = new FinalizableObject2(); // Finalizable2 instance allocated here
obj1 = null; // No more references to the Finalizable1 instance
GC.Collect();
उत्पादन होगा:
<namespace>.FinalizableObject1 initialized
<namespace>.FinalizableObject2 initialized
<namespace>.FinalizableObject1 finalized
जिस समय गारबेज कलेक्टर को आमंत्रित किया जाता है, उस समय फाइनल करने योग्यऑब्जेक्ट 1 एक मृत वस्तु होती है और इसे अंतिम रूप दिया जाता है, जबकि फाइनल करने योग्य ऑबजेक्ट 2 एक जीवित वस्तु है और इसे प्रबंधित हीप पर रखा जाता है।
एकाधिक मृत वस्तुएं
क्या होगा यदि दो (या कई) अन्यथा मृत वस्तुएं एक दूसरे को संदर्भित करती हैं? यह नीचे दिए गए उदाहरण में दिखाया गया है, यह मानते हुए कि अन्य ऑबजेक्ट फाइनलिबल ऑबजेक्ट की एक सार्वजनिक संपत्ति है:
var obj1 = new FinalizableObject1();
var obj2 = new FinalizableObject2();
obj1.OtherObject = obj2;
obj2.OtherObject = obj1;
obj1 = null; // Program no longer references Finalizable1 instance
obj2 = null; // Program no longer references Finalizable2 instance
// But the two objects still reference each other
GC.Collect();
यह निम्न आउटपुट का उत्पादन करता है:
<namespace>.FinalizedObject1 initialized
<namespace>.FinalizedObject2 initialized
<namespace>.FinalizedObject1 finalized
<namespace>.FinalizedObject2 finalized
दो वस्तुओं को अंतिम रूप दिया गया है और एक दूसरे को संदर्भित करने के बावजूद प्रबंधित हीप से मुक्त किया गया है (क्योंकि वास्तव में जीवित वस्तु से उनमें से किसी में भी कोई अन्य संदर्भ मौजूद नहीं है)।
कमजोर संदर्भ
कमजोर संदर्भ हैं ... संदर्भ, अन्य वस्तुओं (उर्फ "लक्ष्य") के लिए, लेकिन "कमजोर" क्योंकि वे उन वस्तुओं को कचरा एकत्र करने से नहीं रोकते हैं। दूसरे शब्दों में, कमजोर संदर्भों की गिनती नहीं होती है जब कचरा कलेक्टर "जीवित" या "मृत" के रूप में वस्तुओं का मूल्यांकन करता है।
निम्नलिखित कोड:
var weak = new WeakReference<FinalizableObject>(new FinalizableObject());
GC.Collect();
उत्पादन का उत्पादन:
<namespace>.FinalizableObject initialized
<namespace>.FinalizableObject finalized
ऑब्जेक्ट को WeakReference चर द्वारा संदर्भित किए जाने के बावजूद प्रबंधित हीप से मुक्त किया गया है (अभी भी जब स्कोप कचरा इकट्ठा किया गया था तब गुंजाइश है)।
परिणाम # 1: किसी भी समय, यह मान लेना असुरक्षित है कि क्या WeakReference लक्ष्य अभी भी प्रबंधित हीप पर आबंटित है या नहीं।
परिणाम # 2: जब भी किसी कार्यक्रम को कमजोर होने के लक्ष्य तक पहुंचने की आवश्यकता होती है, तो दोनों मामलों के लिए कोड प्रदान किया जाना चाहिए, लक्ष्य का अभी भी आवंटित किया जा रहा है या नहीं। लक्ष्य तक पहुँचने की विधि TryGetTarget:
var target = new object(); // Any object will do as target
var weak = new WeakReference<object>(target); // Create weak reference
target = null; // Drop strong reference to the target
// ... Many things may happen in-between
// Check whether the target is still available
if(weak.TryGetTarget(out target))
{
// Use re-initialized target variable
// To do whatever the target is needed for
}
else
{
// Do something when there is no more target object
// The target variable value should not be used here
}
WeakReference का सामान्य संस्करण .Net 4.5 के बाद से उपलब्ध है। सभी फ्रेमवर्क संस्करण एक गैर-जेनेरिक, अनइपेड संस्करण प्रदान करते हैं जो उसी तरह से बनाया गया है और निम्नानुसार जांचा गया है:
var target = new object(); // Any object will do as target
var weak = new WeakReference(target); // Create weak reference
target = null; // Drop strong reference to the target
// ... Many things may happen in-between
// Check whether the target is still available
if (weak.IsAlive)
{
target = weak.Target;
// Use re-initialized target variable
// To do whatever the target is needed for
}
else
{
// Do something when there is no more target object
// The target variable value should not be used here
}
निपटाना () बनाम फाइनल करना
कार्यान्वयन डिस्पोज़ () पद्धति (और किसी भी स्मृति-भारी संसाधनों को मुक्त करने के लिए एक साधन के रूप में IDis प्रयोज्य के रूप में युक्त वर्ग को घोषित करें) जैसे ही ऑब्जेक्ट का उपयोग नहीं किया जाता है। "पकड़" यह है कि डिस्पोज़ () पद्धति को कभी भी मजबूत नहीं किया जाएगा (फाइनल के विपरीत जो हमेशा ऑब्जेक्ट के जीवन के अंत में लागू होता है)।
एक परिदृश्य एक प्रोग्राम है जिसे डिस्पोज़ करना () वस्तुओं पर स्पष्ट रूप से बनाता है:
private void SomeFunction()
{
// Initialize an object that uses heavy external resources
var disposableObject = new ClassThatImplementsIDisposable();
// ... Use that object
// Dispose as soon as no longer used
disposableObject.Dispose();
// ... Do other stuff
// The disposableObject variable gets out of scope here
// The object will be finalized later on (no guarantee when)
// But it no longer holds to the heavy external resource after it was disposed
}
एक अन्य परिदृश्य एक वर्ग को रूपरेखा द्वारा तात्कालिक होने की घोषणा कर रहा है। इस मामले में नया वर्ग आमतौर पर एक बेस क्लास विरासत में मिलता है, उदाहरण के लिए एमवीसी में एक सिस्टम क्लास के उपवर्ग के रूप में एक कंट्रोलर क्लास बनाता है। M.Mc.ControllerBase। जब बेस क्लास आईडीसॉफ़लेट इंटरफ़ेस को लागू करता है, तो यह एक अच्छा संकेत है कि फ्रेमवर्क द्वारा डिस्पोज़ () को ठीक से लागू किया जाएगा - लेकिन फिर से कोई मजबूत गारंटी नहीं है।
इस प्रकार निपटान () एक अंतिम विकल्प के लिए एक विकल्प नहीं है; इसके बजाय, दोनों को अलग-अलग उद्देश्यों के लिए इस्तेमाल किया जाना चाहिए:
- एक अंतिम रूप से अंततः स्मृति लीक से बचने के लिए संसाधनों को मुक्त करता है जो अन्यथा होता है
- निपटान () संसाधनों को मुक्त करता है (संभवतः समान हैं) जैसे ही इन की आवश्यकता नहीं है, समग्र स्मृति आवंटन पर दबाव को कम करने के लिए।
वस्तुओं का उचित निपटान और अंतिम रूप देना
चूंकि डिस्पोज़ () और फाइनलर्स अलग-अलग उद्देश्यों के लिए लक्षित होते हैं, बाहरी मेमोरी-हेवी संसाधनों को प्रबंधित करने वाले एक वर्ग को दोनों को लागू करना चाहिए। परिणाम वर्ग लिख रहा है ताकि यह दो संभावित परिदृश्यों को अच्छी तरह से संभाल ले:
- जब केवल फाइनल किया जाता है
- जब डिस्पोज़ () पहले किया जाता है और बाद में फाइनल किया जाता है
एक समाधान इस तरह से सफाई कोड लिख रहा है कि इसे एक या दो बार चलाने से केवल एक बार इसे चलाने के समान परिणाम होगा। उदाहरण के लिए व्यवहार्यता व्यवहार की प्रकृति पर निर्भर करती है:
- पहले से बंद डेटाबेस कनेक्शन को बंद करने से शायद कोई प्रभाव नहीं पड़ेगा इसलिए यह काम करता है
- कुछ "यूज़ काउंट" को अपडेट करना खतरनाक है और एक बार के बजाय दो बार कॉल करने पर गलत परिणाम देगा।
एक सुरक्षित समाधान डिज़ाइन द्वारा सुनिश्चित कर रहा है कि सफाई कोड को एक बार और केवल एक बार जो भी बाहरी संदर्भ कहा जाता है। यह समर्पित ध्वज का उपयोग करके "क्लासिक तरीका" प्राप्त किया जा सकता है:
public class DisposableFinalizable1: IDisposable
{
private bool disposed = false;
~DisposableFinalizable1() { Cleanup(); }
public void Dispose() { Cleanup(); }
private void Cleanup()
{
if(!disposed)
{
// Actual code to release resources gets here, then
disposed = true;
}
}
}
वैकल्पिक रूप से, गारबेज कलेक्टर एक विशिष्ट विधि प्रदान करता है SuppressFinalize () जो कि डिस्पोजल के लागू होने के बाद अंतिम रूप से लंघन की अनुमति देता है:
public class DisposableFinalizable2 : IDisposable
{
~DisposableFinalizable2() { Cleanup(); }
public void Dispose()
{
Cleanup();
GC.SuppressFinalize(this);
}
private void Cleanup()
{
// Actual code to release resources gets here
}
}