2012-06-02 8 views
244

मैं एंड्रॉइड में मेमोरी लीक पर कुछ लेख पढ़ रहा हूं और इस दिलचस्प वीडियो को Google I/O on the subject से देखा है।जब यह वास्तव में (अनाम) आंतरिक कक्षाओं का उपयोग करने के लिए सुरक्षित रिसाव है?

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

यह मैं क्या समझ में आ रहा है:

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

इस उदाहरण में, मुझे लगता है कि रिसाव का कोई खतरा नहीं है, क्योंकि 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() में कॉल किया जाएगा, तो क्या यह फ़ील्ड के मानों को पुनर्स्थापित करेगा (जैसा कि वे अभिविन्यास परिवर्तन से पहले थे)? क्या यह आंतरिक कक्षाओं के राज्यों को भी बहाल करेगा?

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

+11

[** इस ब्लॉग पोस्ट **] (http://www.androiddesignpatterns.com/2013/04/activitys-threads-memory-leaks.html) और [** यह ब्लॉग पोस्ट **] (http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html) मेमोरी लीक और आंतरिक कक्षाओं के बारे में कुछ अच्छी जानकारी है। :) –

उत्तर

519

सेबस्टियन,

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

इनर क्लास: परिचय

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

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

कचरा संग्रहण और गैर-स्थैतिक कक्षा इनर

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

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

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

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

समाधान: गैर स्थिर इनर क्लासेस

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

क्रियाएँ और दृश्य: परिचय

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

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

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

क्रियाएँ, दृश्य और गैर-स्थिर कक्षा इनर

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

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

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

समाधान: क्रियाएँ और दृश्य

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

Runnables: परिचय

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

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

निष्कर्ष

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

आशा इस मदद करता है,

FuzzicalLogic

+3

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

+1

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

+1

स्पष्टता के लिए बंद करने के संदर्भ को हटाने के लिए संपादित उत्तर। –

संबंधित मुद्दे

 संबंधित मुद्दे