2009-04-24 15 views
5

यह सरल नमूना कोड समस्या का प्रदर्शन करता है। मैं ArrayBlockingQueue बना रहा हूं, और एक धागा जो take() का उपयोग करके इस कतार पर डेटा की प्रतीक्षा करता है। लूप खत्म हो जाने के बाद, सिद्धांत में दोनों कतार और धागे कचरे को एकत्रित किया जा सकता है, लेकिन व्यवहार में मुझे जल्द ही OutOfMemoryError मिलता है। जीसीएड होने के लिए इसे क्या रोक रहा है, और यह कैसे तय किया जा सकता है?OutOfMemoryError - प्रतीक्षा प्रतीक्षा थ्रेड कचरा क्यों नहीं हो सकता है?

/** 
* Produces out of memory exception because the thread cannot be garbage 
* collected. 
*/ 
@Test 
public void checkLeak() { 
    int count = 0; 
    while (true) { 

     // just a simple demo, not useful code. 
     final ArrayBlockingQueue<Integer> abq = new ArrayBlockingQueue<Integer>(2); 
     final Thread t = new Thread(new Runnable() { 
      @Override 
      public void run() { 
       try { 
        abq.take(); 
       } catch (final InterruptedException e) { 
        e.printStackTrace(); 
       } 
      } 
     }); 
     t.start(); 

     // perform a GC once in a while 
     if (++count % 1000 == 0) { 
      System.out.println("gc"); 
      // this should remove all the previously created queues and threads 
      // but it does not 
      System.gc(); 
     } 
    } 
} 

मैं जावा 1.6.0 का उपयोग कर रहा हूं।

अद्यतन: कुछ पुनरावृत्तियों के बाद जीसी प्रदर्शन करें, लेकिन इससे मदद नहीं मिलती है।

उत्तर

8

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

0

आप धागा शुरू करते हैं, इसलिए लूप नए बनाए जाने के दौरान सभी नए थ्रेड असीमित रूप से चल रहे होंगे।

चूंकि आपका कोड लॉक हो रहा है, धागे सिस्टम में जीवन संदर्भ हैं और एकत्र नहीं किए जा सकते हैं। लेकिन अगर वे कुछ काम कर रहे थे, तो धागे जितनी जल्दी हो सके उतनी जल्दी समाप्त होने की संभावना नहीं है (कम से कम इस नमूने में), और इसलिए जीसी सभी मेमोरी एकत्र नहीं कर सकता है और अंत में आउटऑफमेमरी अपवाद के साथ विफल हो जाएगा।

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

+0

मुझे यह समस्या नहीं है, जब भी मैं प्रत्येक लूप में System.gc() चलाता हूं तो धागे नष्ट नहीं होते हैं। – martinus

+1

@ मार्टिनस अधिक सावधानी से पढ़ें। धागे को इकट्ठा करने में कितना समय लगता है धागे को इकट्ठा करने के लिए जितना समय लगता है उतना लंबा होता है। यदि आप एक अस्थिर बाथटब भरना शुरू करते हैं और आप टब नालियों की तुलना में तेज़ी से पानी जोड़ते हैं, तो टब अंततः भर जाएगा और बह जाएगा। यह एक संसाधन आवंटन/deallocation दौड़ की स्थिति है। क्या होता है यदि आप केवल 100 धागे बनाते हैं, और थोड़ी देर में जारी रहते हैं? क्या धागे अंततः एकत्र हो जाते हैं? – Wedge

+0

@ वेज, नहीं, वे कभी एकत्र नहीं हुए! या कुछ और करो। यहां तक ​​कि जब मैं प्रत्येक लूप के अंत में एक सेकंड प्रतीक्षा करता हूं तब भी कभी भी जीसीडी नहीं होता है। – martinus

5

आप अनिश्चित काल तक धागे बना रहे हैं क्योंकि वे ArrayBlockingQueue<Integer> abq तक कुछ प्रविष्टियां हैं। तो अंत में आपको OutOfMemoryError मिल जाएगा।

(संपादित)

प्रत्येक धागा आपके द्वारा बनाए गए अंत कभी नहीं होगा एक प्रविष्टि के रूप में abq कतार जब तक है क्योंकि यह ब्लॉक। यदि थ्रेड चल रहा है, तो जीसी किसी भी ऑब्जेक्ट को इकट्ठा करने वाला नहीं है जिसे थ्रेड संदर्भित करता है जिसमें कतार abq और थ्रेड स्वयं शामिल है।

+0

हां, लेकिन लूप – martinus

+1

से अधिक होने पर कतार का संदर्भ नहीं दिया जाता है, आप क्यों कहते हैं कि लूप खत्म हो गया है? लूप हमेशा के लिए जारी रहता है ... –

+0

मेरा मतलब है कि जब लूप का अंत शीर्ष पर फिर से शुरू होता है, तो पहले बनाई गई कतार और धागे का संदर्भ नहीं दिया जाता है। – martinus

0

आपका जबकि लूप एक अनंत लूप है और यह लगातार नए धागे बना रहा है। यद्यपि आप थ्रेड निष्पादन को जितनी जल्दी बनाते हैं, लेकिन थ्रेड द्वारा कार्य को पूरा करने में लगने वाला समय तब होता है जब वह थ्रेड बनाने में आता है।

इसके अलावा, जबकि लूप के अंदर इसे घोषित करके abq पैरामीटर के साथ क्या कर रहे हैं?

आपके संपादन और अन्य टिप्पणियों के आधार पर। System.gc() एक जीसी चक्र की गारंटी नहीं देता है। अपने धागे के निष्पादन की गति से ऊपर मेरा बयान पढ़ें सृजन की गति से कम है।

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

2
abq.put(0); 

अपना दिन बचाएं।

आपके धागे सभी अपनी कतार के take() पर प्रतीक्षा करते हैं लेकिन आपने कभी भी उन कतारों में कुछ भी नहीं रखा है।

+0

यह स्पष्ट रूप से सही जवाब है! –

0

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

मैं यह नहीं कह सकता कि इसे ठीक करने का सबसे अच्छा तरीका क्या है, क्योंकि मुझे यह जानना होगा कि आप क्या करने की कोशिश कर रहे हैं, लेकिन आप java.util.concurrent को देख सकते हैं कि क्या यह देखने के लिए कक्षाएं हैं या नहीं आप की जरूरत है।

0

System.gc कॉल कुछ भी नहीं करता क्योंकि संग्रह करने के लिए कुछ भी नहीं है। जब एक धागा शुरू होता है तो यह धागे संदर्भ गणना को बढ़ाता है, ऐसा नहीं करने का मतलब यह होगा कि धागा अनिश्चित रूप से समाप्त हो जाएगा। जब थ्रेड की रन विधि पूर्ण होती है, तो थ्रेड की संदर्भ गणना कम हो जाती है। क्या हुआ अगर मुख्य लूप समाप्त हो जाता है और इससे पहले कि धागा किसी भी प्रसंस्करण करने के लिए एक मौका है कचरा संग्रहण करता है, यानी यह ओएस द्वारा abq.take से पहले निलंबित कर दिया है -

while (true) { 
    // just a simple demo, not useful code. 
    // 0 0 - the first number is thread reference count, the second is abq ref count 
    final ArrayBlockingQueue<Integer> abq = new ArrayBlockingQueue<Integer>(2); 
    // 0 1 
    final Thread t = new Thread(new Runnable() { 
     @Override 
     public void run() { 
      try { 
       abq.take(); 
       // 2 2 
      } catch (final InterruptedException e) { 
       e.printStackTrace(); 
      } 
     } 
    }); 
    // 1 1 
    t.start(); 
    // 2 2 (because the run calls abq.take) 
    // after end of loop 
    // 1 1 - each created object's reference count is decreased 
} 

अब, वहाँ एक संभावित रेस स्थिति है कथन निष्पादित किया गया है? जीसी जारी होने के बाद रन विधि abq ऑब्जेक्ट तक पहुंचने का प्रयास करेगी, जो खराब होगी।

दौड़ की स्थिति से बचने के लिए, आपको ऑब्जेक्ट को रन विधि में पैरामीटर के रूप में पास करना चाहिए। मुझे इन दिनों जावा के बारे में निश्चित नहीं है, यह थोड़ी देर हो गया है, इसलिए मैं ऑब्जेक्ट को Runnable से प्राप्त कक्षा में एक कन्स्ट्रक्टर पैरामीटर के रूप में पास करने का सुझाव दूंगा। इस तरह, रन विधि कहने से पहले abq के लिए एक अतिरिक्त संदर्भ है, इस प्रकार यह सुनिश्चित करना कि ऑब्जेक्ट हमेशा वैध है।

+0

जावा जीसी के लिए संदर्भ गिनती का उपयोग नहीं करता है। – TrayMan

+0

एक त्वरित खोज पुष्टि करता है कि कोई संदर्भ गिनती नहीं है। 'संदर्भ गणना' को 'ऑब्जेक्ट के संदर्भों की संख्या' के साथ बदलें। वस्तुओं द्वारा उपयोग की जाने वाली स्मृति केवल तब जारी की जाती है जब ऑब्जेक्ट का कोई संदर्भ नहीं होता है। नमूना कोड में, ऐसे संदर्भ दिए जा रहे हैं जिन पर ओपी को एहसास नहीं हुआ था। – Skizz

+0

यहां तक ​​कि संदर्भ गिनती के साथ, 't.start() 'कॉल को' t' की संदर्भ संख्या बढ़ाने से दौड़ की स्थिति को आसानी से रोका जा सकता है। अब जब 'टी' गुंजाइश से बाहर हो जाता है, तब भी एक संदर्भ गिना जाता है, इसलिए टीसी पर कोई जीसी नहीं होती है। जैसा कि ध्यान दिया गया है, जावा संदर्भ गणना का उपयोग नहीं करता है, लेकिन मूल विचार समान है: जैसे ही 'प्रारंभ() 'विधि को कॉल किया जाता है, न कि थ्रेड को चिह्नित किया जाता है (और इसलिए जीसी से रोका जाता है) निष्पादन शुरू होता है। –