जब वास्तव में यह लीक (अनाम) आंतरिक वर्गों का उपयोग करने के लिए सुरक्षित है?


324

मैं एंड्रॉइड में मेमोरी लीक पर कुछ लेख पढ़ रहा हूं और इस विषय पर Google I / O से यह दिलचस्प वीडियो देखा ।

फिर भी, मैं अवधारणा को पूरी तरह से नहीं समझता हूं, और विशेष रूप से तब जब यह किसी गतिविधि के अंदर उपयोगकर्ता कक्षाओं में सुरक्षित या खतरनाक हो ।

यह वही है जो मैंने समझा:

यदि एक आंतरिक वर्ग का उदाहरण उसके बाहरी वर्ग (एक गतिविधि) से अधिक समय तक जीवित रहता है, तो मेमोरी रिसाव हो सकता है। -> यह किन स्थितियों में हो सकता है?

इस उदाहरण में, मुझे लगता है कि रिसाव का कोई खतरा नहीं है, क्योंकि जिस तरह से अनाम वर्ग का विस्तार OnClickListenerहो रहा है वह गतिविधि की तुलना में अधिक समय तक जीवित रहेगा, है ना?

    final Dialog dialog = new Dialog(this);
    dialog.setContentView(R.layout.dialog_generic);
    Button okButton = (Button) dialog.findViewById(R.id.dialog_button_ok);
    TextView titleTv = (TextView) dialog.findViewById(R.id.dialog_generic_title);

    // *** Handle button click
    okButton.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            dialog.dismiss();
        }
    });

    titleTv.setText("dialog title");
    dialog.show();

अब, क्या यह उदाहरण खतरनाक है, और क्यों?

// We are still inside an Activity
_handlerToDelayDroidMove = new Handler();
_handlerToDelayDroidMove.postDelayed(_droidPlayRunnable, 10000);

private Runnable _droidPlayRunnable = new Runnable() { 
    public void run() {
        _someFieldOfTheActivity.performLongCalculation();
    }
};

मुझे इस तथ्य के बारे में संदेह है कि इस विषय को समझने के लिए विस्तार से समझने के साथ क्या करना है जब एक गतिविधि नष्ट हो जाती है और फिर से बनाई जाती है।

क्या यह?

मान लें कि मैंने केवल डिवाइस का ओरिएंटेशन बदल दिया है (जो कि लीक का सबसे आम कारण है)। जब super.onCreate(savedInstanceState)मुझे बुलाया onCreate()जाएगा, तो क्या यह खेतों के मूल्यों को बहाल करेगा (जैसा कि वे अभिविन्यास परिवर्तन से पहले थे)? क्या इससे आंतरिक वर्गों की स्थिति भी बहाल होगी?

मुझे पता है कि मेरा सवाल बहुत सटीक नहीं है, लेकिन मैं वास्तव में किसी भी स्पष्टीकरण की सराहना करूंगा जो चीजों को स्पष्ट कर सकता है।


14
इस ब्लॉग पोस्ट और इस ब्लॉग पोस्ट में मेमोरी लीक और आंतरिक कक्षाओं के बारे में कुछ अच्छी जानकारी है। :)
एलेक्स लॉकवुड

जवाबों:


651

आप जो पूछ रहे हैं वह बहुत कठिन सवाल है। जबकि आप सोच सकते हैं कि यह सिर्फ एक सवाल है, आप वास्तव में एक साथ कई सवाल पूछ रहे हैं। मैं इस ज्ञान के साथ अपना सर्वश्रेष्ठ प्रदर्शन करूंगा कि मुझे इसे कवर करना है और, उम्मीद है कि कुछ अन्य लोग मुझे याद करने के लिए शामिल होंगे।

नेस्टेड क्लासेस: परिचय

जैसा कि मुझे यकीन नहीं है कि आप जावा में ओओपी के साथ कितने सहज हैं, यह कुछ बुनियादी बातों को प्रभावित करेगा। एक नेस्टेड क्लास तब होता है जब एक क्लास डेफिनेशन दूसरे क्लास के अंदर समाहित होता है। मूल रूप से दो प्रकार हैं: स्टेटिक नेस्टेड क्लास और इनर क्लास। इनमें से वास्तविक अंतर हैं:

  • स्टेटिक नेस्टेड क्लासेस:
    • "टॉप-लेवल" माने जाते हैं।
    • निर्माण किए जाने वाले वर्ग के एक उदाहरण की आवश्यकता नहीं है।
    • स्पष्ट संदर्भ के बिना वर्ग के सदस्यों का संदर्भ न लें।
    • उनका अपना जीवनकाल है।
  • इनर नेस्टेड क्लासेस:
    • हमेशा निर्माण किए जाने वाले वर्ग के एक उदाहरण की आवश्यकता होती है।
    • स्वचालित रूप से युक्त उदाहरण के लिए एक अंतर्निहित संदर्भ है।
    • संदर्भ के बिना कंटेनर के वर्ग सदस्यों तक पहुंच हो सकती है।
    • लाइफटाइम माना जाता है कि कंटेनर की तुलना में अब नहीं है।

कचरा संग्रह और आंतरिक कक्षाएं

कचरा संग्रह स्वचालित है, लेकिन उन वस्तुओं को हटाने की कोशिश करता है जो यह सोचते हैं कि उनका उपयोग किया जा रहा है। कचरा कलेक्टर बहुत स्मार्ट है, लेकिन निर्दोष नहीं है। यह केवल यह निर्धारित कर सकता है कि किसी वस्तु का उपयोग किया जा रहा है या नहीं।

यहाँ असली मुद्दा तब है जब एक आंतरिक वर्ग को उसके कंटेनर की तुलना में लंबे समय तक जीवित रखा गया है। इसका कारण है कि इसमें निहित वर्ग का संदर्भ है। इसका एकमात्र तरीका यह हो सकता है कि यदि युक्त वर्ग के बाहर की वस्तु, आंतरिक वस्तु के संदर्भ में रहती है, जिसमें युक्त वस्तु की परवाह किए बिना।

यह एक ऐसी स्थिति को जन्म दे सकता है जहां आंतरिक वस्तु जीवित है (संदर्भ के माध्यम से), लेकिन युक्त वस्तु के संदर्भ को अन्य सभी वस्तुओं से पहले ही हटा दिया गया है। इसलिए, आंतरिक वस्तु है, जिसमें युक्त वस्तु को जीवित रखना है क्योंकि यह हमेशा इसका संदर्भ होगा। इसके साथ समस्या यह है कि जब तक यह प्रोग्राम नहीं किया जाता है, तब तक जीवित रहने के लिए जाँच करने के लिए युक्त वस्तु पर वापस जाने का कोई तरीका नहीं है।

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

समाधान: इनर क्लासेस

  • युक्त ऑब्जेक्ट से अस्थायी संदर्भ प्राप्त करें।
  • भीतर की वस्तुओं के लिए लंबे समय तक रहने वाले संदर्भों को रखने के लिए केवल युक्त वस्तु को अनुमति दें।
  • फैक्ट्री जैसे स्थापित पैटर्न का उपयोग करें।
  • यदि आंतरिक वर्ग में शामिल वर्ग के सदस्यों के लिए पहुँच की आवश्यकता नहीं है, तो इसे एक स्थिर वर्ग में बदलने पर विचार करें।
  • चाहे वह किसी गतिविधि में हो या न हो, सावधानी के साथ प्रयोग करें।

गतिविधियाँ और दृश्य: परिचय

गतिविधियों में चलाने और प्रदर्शित करने में सक्षम होने के लिए बहुत सारी जानकारी होती है। गतिविधियों को इस विशेषता से परिभाषित किया जाता है कि उनके पास एक दृश्य होना चाहिए। उनके पास कुछ स्वचालित हैंडलर भी हैं। आप इसे निर्दिष्ट करते हैं या नहीं, गतिविधि में इसका जो दृश्य है उसका निहित संदर्भ है।

व्यू बनाने के लिए, यह पता होना चाहिए कि इसे कहां बनाना है और क्या इसके कोई बच्चे हैं ताकि यह प्रदर्शित हो सके। इसका अर्थ है कि प्रत्येक दृश्य में गतिविधि (के माध्यम से getContext()) का संदर्भ है । इसके अलावा, हर दृश्य अपने बच्चों (यानी getChildAt()) का संदर्भ रखता है । अंत में, प्रत्येक दृश्य प्रदान किए गए बिटमैप के लिए एक संदर्भ रखता है जो इसके प्रदर्शन का प्रतिनिधित्व करता है।

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

गतिविधियाँ, दृश्य और आंतरिक कक्षाएं

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

लीक की गई गतिविधियां, दृश्य और गतिविधि के संदर्भ

यह सब प्रसंग और जीवनचक्र के लिए नीचे आता है। कुछ घटनाएँ (जैसे अभिविन्यास) हैं जो एक गतिविधि संदर्भ को मार देंगी। चूंकि कई वर्गों और विधियों को एक संदर्भ की आवश्यकता होती है, डेवलपर्स कभी-कभी किसी संदर्भ के संदर्भ को पकड़कर और उस पर पकड़ बनाकर कुछ कोड को सहेजने की कोशिश करेंगे। यह सिर्फ इतना होता है कि हमें अपनी गतिविधि को चलाने के लिए बनाने के लिए कई वस्तुओं को गतिविधि जीवन चक्र के बाहर मौजूद होना चाहिए ताकि गतिविधि को वह करने की अनुमति मिल सके जो उसे करने की आवश्यकता है। यदि आपकी किसी भी वस्तु के नष्ट होने पर किसी गतिविधि, उसके संदर्भ या उसके किसी भी दृश्य का संदर्भ होता है, तो आपने उस गतिविधि और उसके संपूर्ण दृश्य ट्री को लीक कर दिया है।

समाधान: गतिविधियाँ और दृश्य

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

Runnables: परिचय

Runnables वास्तव में खराब नहीं हैं। मेरा मतलब है, वे हो सकते हैं, लेकिन वास्तव में हम पहले से ही अधिकांश खतरे क्षेत्रों को मार चुके हैं। एक रननेबल एक अतुल्यकालिक ऑपरेशन है जो उस कार्य से स्वतंत्र कार्य करता है, जिस पर इसे बनाया गया था। अधिकांश रनवे को UI थ्रेड से त्वरित किया जाता है। संक्षेप में, एक रननेबल का उपयोग करके एक और धागा बनाया जा रहा है, बस थोड़ा और अधिक प्रबंधित। यदि आप एक मानक वर्ग की तरह एक रनवेबल को वर्गीकृत करते हैं और उपरोक्त दिशानिर्देशों का पालन करते हैं, तो आपको कुछ समस्या में भाग लेना चाहिए। वास्तविकता यह है कि कई डेवलपर्स ऐसा नहीं करते हैं।

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

Runnables और क्रियाएँ / दृश्य

वाह! यह खंड छोटा हो सकता है! इस तथ्य के कारण कि Runnables वर्तमान थ्रेड के बाहर चलते हैं, इन के साथ खतरा लंबे समय तक चलने वाले अतुल्यकालिक संचालन के लिए आता है। यदि रन करने योग्य को किसी गतिविधि या दृश्य में बेनामी इनर क्लास या नेस्टेड इनर क्लास के रूप में परिभाषित किया गया है, तो कुछ बहुत गंभीर खतरे हैं। ऐसा इसलिए, क्योंकि जैसा कि पहले कहा गया है, यह है पता है, जो अपने कंटेनर है। अभिविन्यास परिवर्तन (या सिस्टम किल) दर्ज करें। अब बस पिछले अनुभागों को समझने के लिए वापस देखें कि क्या हुआ था। हां, आपका उदाहरण काफी खतरनाक है।

समाधान: Runnables

  • कोशिश करें और Runnable का विस्तार करें, अगर यह आपके कोड के तर्क को नहीं तोड़ता है।
  • विस्तारित रनवेबल को स्थिर बनाने के लिए अपना सर्वश्रेष्ठ करें, यदि उन्हें नेस्टेड क्लास होना चाहिए।
  • यदि आपको बेनामी रनवेबल्स का उपयोग करना है, तो उन्हें किसी भी ऑब्जेक्ट में बनाने से बचें, जिसका उपयोग गतिविधि या दृश्य में लंबे समय तक रहता है।
  • कई Runnables बस के रूप में आसानी से AsyncTasks किया जा सकता था। AsyncTask का उपयोग करने पर विचार करें क्योंकि वे डिफ़ॉल्ट रूप से VM प्रबंधित हैं।

इस पोस्ट के अन्य अनुभागों द्वारा सीधे पूछे गए प्रश्नों का उत्तर देने के लिए अंतिम प्रश्न का उत्तर देना । आपने पूछा "जब आंतरिक वर्ग की कोई वस्तु अपने बाहरी वर्ग से अधिक समय तक जीवित रह सकती है?" इससे पहले कि हम इसे प्राप्त करें, मुझे पुनर्मूल्यांकन करें: यद्यपि आप गतिविधियों में इस बारे में चिंता करने के लिए सही हैं, यह कहीं भी रिसाव का कारण बन सकता है। मैं एक सरल उदाहरण प्रदान करूँगा (बिना किसी गतिविधि का उपयोग किए) केवल प्रदर्शित करने के लिए।

नीचे बेसिक फैक्ट्री का एक सामान्य उदाहरण है (कोड गायब है)।

public class LeakFactory
{//Just so that we have some data to leak
    int myID = 0;
// Necessary because our Leak class is an Inner class
    public Leak createLeak()
    {
        return new Leak();
    }

// Mass Manufactured Leak class
    public class Leak
    {//Again for a little data.
       int size = 1;
    }
}

यह एक सामान्य उदाहरण के रूप में नहीं है, लेकिन प्रदर्शन के लिए काफी सरल है। यहाँ कुंजी निर्माता है ...

public class SwissCheese
{//Can't have swiss cheese without some holes
    public Leak[] myHoles;

    public SwissCheese()
    {//Gotta have a Factory to make my holes
        LeakFactory _holeDriller = new LeakFactory()
    // Now, let's get the holes and store them.
        myHoles = new Leak[1000];

        for (int i = 0; i++; i<1000)
        {//Store them in the class member
            myHoles[i] = _holeDriller.createLeak();
        }

    // Yay! We're done! 

    // Buh-bye LeakFactory. I don't need you anymore...
    }
}

अब, हमारे पास लीक्स हैं, लेकिन कोई फैक्ट्री नहीं। भले ही हमने कारखाना जारी किया, लेकिन यह स्मृति में रहेगा क्योंकि हर एक लीक का एक संदर्भ है। इससे कोई फर्क नहीं पड़ता कि बाहरी वर्ग के पास कोई डेटा नहीं है। ऐसा लगता है कि एक से अधिक बार होता है। हमें रचनाकार की जरूरत नहीं है, बस उसकी रचनाओं की। इसलिए हम एक अस्थायी रूप से बनाते हैं, लेकिन अनिश्चित काल तक रचनाओं का उपयोग करते हैं।

कल्पना कीजिए कि जब हम कंस्ट्रक्टर को थोड़े से बदलते हैं तो क्या होता है।

public class SwissCheese
{//Can't have swiss cheese without some holes
    public Leak[] myHoles;

    public SwissCheese()
    {//Now, let's get the holes and store them.
        myHoles = new Leak[1000];

        for (int i = 0; i++; i<1000)
        {//WOW! I don't even have to create a Factory... 
        // This is SOOOO much prettier....
            myHoles[i] = new LeakFactory().createLeak();
        }
    }
}

अब, उन नए लीकफैक्टरीज में से हर एक को अभी-अभी लीक किया गया है। उसके बारे में क्या ख़याल है? वे दो बहुत सामान्य उदाहरण हैं कि कैसे एक आंतरिक वर्ग किसी भी प्रकार के बाहरी वर्ग को प्रभावित कर सकता है। अगर वह बाहरी वर्ग एक गतिविधि था, तो कल्पना कीजिए कि यह कितना बुरा था।

निष्कर्ष

इन वस्तुओं को अनुचित तरीके से उपयोग करने के मुख्य रूप से ज्ञात खतरों की सूची है। सामान्य तौर पर, इस पोस्ट को आपके अधिकांश प्रश्नों को कवर करना चाहिए था, लेकिन मैं समझता हूं कि यह एक छोटा पोस्ट था, इसलिए यदि आपको स्पष्टीकरण की आवश्यकता है, तो मुझे बताएं। जब तक आप उपरोक्त प्रथाओं का पालन करते हैं, आपको रिसाव की बहुत कम चिंता होगी।


3
इस स्पष्ट और विस्तृत उत्तर के लिए बहुत बहुत धन्यवाद। मुझे नहीं लगता है कि आपके द्वारा "कई डेवलपर अपने रनवे को परिभाषित करने के लिए क्लोजर का उपयोग करते हैं"
Sbbastien

1
जावा में क्लोजर बेनामी इनर क्लासेस हैं, जैसे कि रननेबल आप वर्णन करते हैं। इसकी एक कक्षा का उपयोग करने का एक तरीका है (लगभग इसे बढ़ाएं) एक परिभाषित वर्ग को लिखे बिना जो रननीय को बढ़ाता है। इसे एक बंद कहा जाता है क्योंकि यह "एक बंद वर्ग परिभाषा" है, जिसमें वास्तविक युक्त वस्तु के भीतर इसकी अपनी बंद स्मृति स्थान है।
फ़िज़िकल लॉजिक

26
ज्ञानवर्धक लेखन! शब्दावली के संबंध में एक टिप्पणी: जावा में स्थिर आंतरिक वर्ग जैसी कोई चीज नहीं है । ( डॉक्स )। एक नेस्टेड क्लास या तो स्थिर या आंतरिक है , लेकिन एक ही समय में दोनों नहीं हो सकते।
जेंज

2
जबकि यह तकनीकी रूप से सही है, जावा आपको स्थिर कक्षाओं के अंदर स्थिर वर्गों को परिभाषित करने की अनुमति देता है। शब्दावली मेरे लाभ के लिए नहीं है, बल्कि अन्य लोगों के लाभ के लिए है जो तकनीकी शब्दार्थ को नहीं समझते हैं। यही कारण है कि यह पहली बार उल्लेख किया गया है कि वे "शीर्ष-स्तर" हैं। Android डेवलपर डॉक्स भी इस शब्दावली का उपयोग करते हैं, और यह एंड्रॉइड विकास को देखने वाले लोगों के लिए है, इसलिए मैंने निरंतरता रखना बेहतर समझा।
फ़िज़िकल लॉजिक

13
ग्रेट पोस्ट, StackOverflow में सबसे अच्छे में से एक, Android के लिए esp।
स्टैकऑवरफ्लो जुले
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.