जावा में मेमोरी लीक कैसे बनाएं?


3221

मेरे पास बस एक साक्षात्कार था, और मुझे जावा के साथ एक मेमोरी लीक बनाने के लिए कहा गया था ।
कहने की जरूरत नहीं है, मुझे लगा कि कोई भी कैसे शुरू करने के लिए कोई सुराग नहीं होने पर बहुत गूंगा लगा।

एक उदाहरण क्या होगा?


275
मैं उनसे कहता है कि जावा एक कचरा कलेक्टर का उपयोग करता है, और उनसे "स्मृति रिसाव" की अपनी परिभाषा के बारे में थोड़ा और अधिक विशिष्ट हो सकता है, यह बताता है कि - JVM कीड़े को छोड़कर - जावा में स्मृति लीक नहीं कर सकते हैं काफी उसी तरह सी / सी ++। आपको वस्तु का संदर्भ कहीं न कहीं रखना होगा
Darien

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

62
ईमानदारी से मैं उसी तरह के प्रश्न पर विश्वास नहीं कर सकता, जो मैंने "गो" के बारे में पूछा था - -1 से नीचे हो गया। यहाँ: stackoverflow.com/questions/4400311/… मूल रूप से मेमोरी लीक के बारे में मैं बात कर रहा था, जो ओपी को +200 अपवोट्स मिले हैं और फिर भी मुझे यह पूछने पर अपमानित किया गया कि क्या "गो" का भी यही मुद्दा है। किसी तरह मुझे यकीन नहीं है कि सभी विकी-चीज़ उस महान काम कर रहे हैं।
SyntaxT3rr0r

74
@ SyntaxT3rr0r - डेरेन का जवाब कट्टरता नहीं है। उन्होंने स्पष्ट रूप से स्वीकार किया कि कुछ JVM में बग्स हो सकते हैं, जिसका अर्थ है कि मेमोरी लीक हो जाती है। यह मेमोरी स्पेक के लिए अनुमति देने वाले भाषा विशेष से अलग है।
पीटर रिकोर

31
@ehabkost: नहीं, वे समकक्ष नहीं हैं। (1) आपके पास मेमोरी को पुनः प्राप्त करने की क्षमता है, जबकि "सही लीक" में आपका सी / सी ++ प्रोग्राम उस सीमा को भूल जाता है जिसे आवंटित किया गया था, जिसे पुनर्प्राप्त करने का कोई सुरक्षित तरीका नहीं है। (2) आप बहुत आसानी से प्रोफाइलिंग के साथ समस्या का पता लगा सकते हैं, क्योंकि आप देख सकते हैं कि किन चीजों में "ब्लोट" शामिल है। (3) एक "सच्ची लीक" एक असमान त्रुटि है, जबकि एक कार्यक्रम जो समाप्त होने तक बहुत सारी वस्तुओं को रखता है, यह एक जानबूझकर हिस्सा हो सकता है कि यह कैसे काम करने के लिए है।
डेरेन

जवाबों:


2309

यहां शुद्ध जावा में एक सच्ची मेमोरी लीकेज (कोड से चलकर लेकिन अभी भी मेमोरी में संग्रहित होने वाली वस्तुएं) बनाने का एक अच्छा तरीका है:

  1. एप्लिकेशन लंबे समय तक चलने वाला धागा बनाता है (या तेज़ी से लीक करने के लिए थ्रेड पूल का उपयोग करता है)।
  2. धागा एक (वैकल्पिक रूप से कस्टम) के माध्यम से एक वर्ग को लोड करता है ClassLoader
  3. वर्ग स्मृति का एक बड़ा हिस्सा आवंटित करता है (जैसे new byte[1000000]), एक स्थिर क्षेत्र में इसके लिए एक मजबूत संदर्भ संग्रहीत करता है, और फिर एक में खुद के लिए एक संदर्भ संग्रहीत करता है ThreadLocal। अतिरिक्त मेमोरी आवंटित करना वैकल्पिक है (कक्षा के उदाहरण को लीक करना पर्याप्त है), लेकिन यह रिसाव का काम इतना तेज कर देगा।
  4. आवेदन कस्टम वर्ग के सभी संदर्भों को साफ करता है या ClassLoaderइसे लोड किया गया था।
  5. दोहराएँ।

ThreadLocalओरेकल के JDK में लागू होने के तरीके के कारण , यह मेमोरी लीक बनाता है:

  • प्रत्येक Threadमें एक निजी क्षेत्र है threadLocals, जो वास्तव में थ्रेड-स्थानीय मानों को संग्रहीत करता है।
  • इस मानचित्र की प्रत्येक कुंजी किसी ThreadLocalवस्तु के लिए एक कमजोर संदर्भ है , इसलिए उस ThreadLocalवस्तु को कचरा एकत्र करने के बाद, उसका प्रवेश मानचित्र से हटा दिया जाता है।
  • लेकिन प्रत्येक मान एक मजबूत संदर्भ है, इसलिए जब कोई मान (प्रत्यक्ष या अप्रत्यक्ष रूप से) उस ThreadLocalवस्तु को इंगित करता है जो इसकी कुंजी है , तो वह वस्तु न तो कचरा एकत्र करेगी और न ही मानचित्र से निकालेगी जब तक धागा रहता है।

इस उदाहरण में, मजबूत संदर्भों की श्रृंखला इस तरह दिखती है:

Threadऑब्जेक्ट → threadLocalsनक्शा → उदाहरण वर्ग का उदाहरण → उदाहरण वर्ग → स्थिर ThreadLocalफ़ील्ड → ThreadLocalऑब्जेक्ट।

(यह ClassLoaderवास्तव में लीक को बनाने में कोई भूमिका नहीं निभाता है, यह सिर्फ इस अतिरिक्त संदर्भ श्रृंखला के कारण रिसाव को बदतर बनाता है: उदाहरण वर्ग → ClassLoader→ यह सभी वर्ग जो इसे लोड किया गया है। यह कई JVM कार्यान्वयनों में और भी बुरा था, विशेष रूप से पूर्व। जावा 7, क्योंकि कक्षाओं और ClassLoaderएस को सीधे परमिटेन में आवंटित किया गया था और कभी भी कचरा एकत्र नहीं किया गया था।

इस पैटर्न पर एक भिन्नता यह है कि क्यों अनुप्रयोग कंटेनर (जैसे टॉमकैट) एक छलनी की तरह मेमोरी को लीक कर सकते हैं यदि आप अक्सर एप्लिकेशन को फिर से तैयार करते हैं जो ThreadLocalकि किसी तरह से एस का उपयोग करने के लिए होता है । यह कई सूक्ष्म कारणों से हो सकता है और अक्सर डिबग और / या ठीक करना मुश्किल होता है।

अपडेट : चूंकि बहुत से लोग इसके लिए पूछते रहते हैं, इसलिए यहां कुछ उदाहरण कोड हैं जो कार्रवाई में इस व्यवहार को दर्शाता है


186
+1 ClassLoader लीक JEE की दुनिया में सबसे आम तौर पर दर्दनाक मेमोरी लीक में से कुछ हैं, जो अक्सर डेटा (बीन्यूटिल्स, एक्सएमएल / JSON कोडेक्स) को बदलने वाले 3 पार्टी कामों के कारण होता है। यह तब हो सकता है जब आपके एप्लिकेशन के रूट क्लास लोडर के बाहर lib लोड किया जाता है, लेकिन आपकी कक्षाओं (जैसे कैशिंग द्वारा) के संदर्भ रखता है। जब आप अपने ऐप को अनइंस्टॉल / रिडायलेट करते हैं, तो JVM ऐप के क्लास लोडर (और इसलिए उसके द्वारा लोड की गई सभी कक्षाएं) को इकट्ठा करने में कचरा नहीं कर पाता है, इसलिए रिपीट डिप्लॉय्स के साथ ऐप सर्वर अंततः बोर हो जाता है। यदि भाग्यशाली हो तो आपको ClassCastException zxyAbc के साथ एक सुराग मिल सकता है zxyAbc पर नहीं डाला जा सकता है
earcam

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

57
+1: क्लास लोडर लीक एक बुरा सपना है। मैंने उन्हें पता लगाने के लिए कई सप्ताह बिताए। दुखद बात यह है, जैसा कि @earcam ने कहा है, वे ज्यादातर 3rd पार्टी लिबास के कारण होते हैं और अधिकांश प्रोफाइलर भी इन लीक का पता नहीं लगा सकते हैं। क्लासलोडर लीक के बारे में इस ब्लॉग पर एक अच्छी और स्पष्ट व्याख्या है। blogs.oracle.com/fkieviet/entry/…
एड्रियन एम

4
@ निकोलस: क्या आपको यकीन है? JRockit डिफ़ॉल्ट रूप से GC क्लास ऑब्जेक्ट करता है, और HotSpot नहीं करता है, लेकिन AFAIK JRockit अभी भी GC या ClassLader को GC नहीं कर सकता है जो एक ThreadLocal द्वारा संदर्भित है।
डैनियल प्राइडेन

6
टॉमकैट आपके लिए इन लीक का पता लगाने की कोशिश करेगा, और उनके बारे में चेतावनी देगा : wiki.apache.org/tomcat/MemoryLeakProtection । सबसे हाल का संस्करण कभी-कभी आपके लिए रिसाव को भी ठीक कर देगा।
मैथिज्स बिरमन

1212

स्थैतिक क्षेत्र धारण वस्तु संदर्भ [esp अंतिम क्षेत्र]

class MemorableClass {
    static final ArrayList list = new ArrayList(100);
}

String.intern()लम्बी स्ट्रिंग पर कॉल करना

String str=readString(); // read lengthy string any source db,textbox/jsp etc..
// This will place the string in memory pool from which you can't remove
str.intern();

(खुला) खुली धाराएँ (फ़ाइल, नेटवर्क आदि ...)

try {
    BufferedReader br = new BufferedReader(new FileReader(inputFile));
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

बिना कनेक्शन के

try {
    Connection conn = ConnectionFactory.getConnection();
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

ऐसे क्षेत्र जो जेवीएम के कचरा संग्राहक से अप्राप्य हैं , जैसे कि देशी तरीकों के माध्यम से आवंटित की गई मेमोरी

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

getServletContext().setAttribute("SOME_MAP", map);

गलत या अनुचित JVM विकल्प , जैसे noclassgcIBM JDK पर विकल्प जो अप्रयुक्त वर्ग कचरा संग्रह को रोकता है

IBM jdk सेटिंग देखें ।


178
मैं इस बात से असहमत हूँ कि संदर्भ और सत्र विशेषताएँ "लीक" हैं। वे सिर्फ लंबे समय तक जीवित चर रहे हैं। और स्थिर अंतिम क्षेत्र कमोबेश केवल एक स्थिरांक है। शायद बड़े स्थिरांक से बचा जाना चाहिए, लेकिन मुझे नहीं लगता कि इसे मेमोरी लीक कहना उचित है।
इयान मैकलेरड

80
(खुला) खुली धाराएँ (फ़ाइल, नेटवर्क आदि ...) , अंतिम रूप देने के दौरान (जो अगले जीसी चक्र के बाद होगी) () close()आमतौर पर अंतिम रूप में लागू नहीं होने वाली है थ्रेड चूंकि ब्लॉकिंग ऑपरेशन हो सकता है)। यह बंद नहीं करने के लिए एक बुरा अभ्यास है, लेकिन यह रिसाव का कारण नहीं बनता है। अनक्लोज्ड java.sql.Connection एक ही है।
बेस्ट

33
अधिकांश समझदार JVM में, ऐसा प्रतीत होता है जैसे स्ट्रिंग वर्ग में केवल अपनी internहैशटेबल सामग्री पर एक कमजोर संदर्भ है । इस प्रकार, यह है कचरा ठीक से एकत्र और एक रिसाव नहीं। (लेकिन IANAJP) mindprod.com/jgloss/interned.html#GC
मैट बी।

42
कैसे स्थैतिक क्षेत्र वस्तु संदर्भ [esp अंतिम क्षेत्र] एक स्मृति रिसाव है ??
कानागावेलु सुगम कुमार

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

460

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

यदि आप चाहते हैं कि ये ख़राब कुंजियाँ / तत्व आपके चारों ओर घूमने के लिए स्थिर क्षेत्र का उपयोग कर सकें

class BadKey {
   // no hashCode or equals();
   public final String key;
   public BadKey(String key) { this.key = key; }
}

Map map = System.getProperties();
map.put(new BadKey("key"), "value"); // Memory leak even if your threads die.

68
वास्तव में, आप तत्वों को हैशसेट से हटा सकते हैं, भले ही तत्व वर्ग हैशकोड हो और गलत के बराबर हो; बस सेट के लिए एक इटरेटर प्राप्त करें और इसकी हटाने की विधि का उपयोग करें, क्योंकि इटरेटर वास्तव में अंतर्निहित प्रविष्टियों को स्वयं संचालित करता है न कि तत्वों को। (ध्यान दें कि एक अनइम्प्लीमेंटेड हैशकोड / बराबर एक रिसाव को ट्रिगर करने के लिए पर्याप्त नहीं है; चूक साधारण वस्तु पहचान को लागू करती है और इसलिए आप तत्वों को प्राप्त कर सकते हैं और उन्हें सामान्य रूप से हटा सकते हैं।)
डोनल फैलो

12
@ जो मैं कहने की कोशिश कर रहा हूं, मुझे लगता है, क्या मैं स्मृति रिसाव की आपकी परिभाषा से असहमत हूं। मैं एक रिसाव के तहत ड्रिप-पैन बनने के लिए आपकी पुनरावृत्ति-हटाने की तकनीक (सादृश्य को जारी रखना) पर विचार करूंगा; रिसाव अभी भी ड्रिप पैन की परवाह किए बिना मौजूद है।
corsiKa

94
मैं मानता हूँ, यह है नहीं , एक स्मृति "रिसाव" क्योंकि आप बस HashSet के संदर्भ निकालें और में, और Presto लात करने के लिए जी सी के लिए प्रतीक्षा कर सकते हैं! स्मृति वापस चली जाती है।
user541686

12
@ SyntaxT3rr0r, मैंने आपके प्रश्न की व्याख्या करते हुए पूछा कि क्या भाषा में कुछ भी है जो स्वाभाविक रूप से मेमोरी लीक की ओर जाता है। जवाब न है। यह प्रश्न पूछता है कि क्या मेमोरी लीक जैसी किसी चीज को बनाने के लिए स्थिति को नियंत्रित करना संभव है। इन उदाहरणों में से कोई भी मेमोरी लीक नहीं है जिस तरह से एक C / C ++ प्रोग्रामर इसे समझ जाएगा।
पीटर लॉरी

11
@Peter Lawrey: इसके अलावा, आप इस बारे में क्या सोचते हैं: "सी भाषा में कुछ भी नहीं है जो स्वाभाविक रूप से मेमोरी लीक पर लीक हो जाता है यदि आप मैन्युअल रूप से आवंटित की गई स्मृति को मैन्युअल रूप से मुक्त नहीं करना चाहते हैं" । बौद्धिक बेईमानी के लिए यह कैसा होगा? वैसे भी, मैं थका हुआ हूं: आपके पास अंतिम शब्द हो सकता है।
SyntaxT3rr0r

271

नीचे एक गैर-स्पष्ट मामला होगा जहां जावा लीक, भूल गए श्रोताओं के मानक मामले के अलावा, हैशैप्स में स्थैतिक संदर्भ, फर्जी / परिवर्तनीय कुंजी, या अपने जीवन-चक्र को समाप्त करने के लिए बस बिना किसी अवसर के अटक गए धागे।

  • File.deleteOnExit() - हमेशा स्ट्रिंग लीक करता है, यदि स्ट्रिंग एक विकल्प है, तो रिसाव और भी बदतर है (अंतर्निहित चार [] भी लीक है)- जावा 7 में सबस्ट्रिंग भी कॉपी करता है char[], इसलिए बाद में लागू नहीं होता है ; @ दानिएल को वोटों की कोई ज़रूरत नहीं है, हालाँकि।

मैं थ्रेड्स पर ध्यान केंद्रित करूंगा, मानवरहित धागों के खतरे को दिखाने के लिए ज्यादातर, स्विंग को छूने की इच्छा नहीं रखते हैं।

  • Runtime.addShutdownHookऔर नहीं हटाएं ... और फिर भी RemoveShutdownHook थ्रेडग्रुप क्लास में बग के कारण अनस्टार्ट किए गए थ्रेड्स के बारे में यह एकत्र नहीं हो सकता है, थ्रेडग्रुप को प्रभावी ढंग से लीक कर सकता है। JGroup का GossipRouter में रिसाव है।

  • बनाना, लेकिन शुरू नहीं करना, Threadऊपर के समान श्रेणी में जाता है।

  • एक धागा बनाना विरासत में मिला है ContextClassLoaderऔर AccessControlContext, प्लस ThreadGroupऔर कोई भी InheritedThreadLocal, वे सभी संदर्भ संभावित लीक हैं, साथ ही क्लास लोडर और सभी स्थिर संदर्भों द्वारा लोड किए गए पूरे वर्गों और जेए-जेए के साथ। प्रभाव विशेष रूप से पूरे jucExecutor ढांचे के साथ दिखाई देता है जो एक सुपर सरल ThreadFactoryइंटरफ़ेस की सुविधा देता है , फिर भी अधिकांश डेवलपर्स के पास गुप्त खतरे का कोई सुराग नहीं है। इसके अलावा बहुत सारे पुस्तकालय अनुरोध पर धागे शुरू करते हैं (जिस तरह कई उद्योग लोकप्रिय पुस्तकालय हैं)।

  • ThreadLocalकैश; वे कई मामलों में बुरे हैं। मुझे यकीन है कि सभी ने थ्रेडलोकल के आधार पर काफी सरल कैचेस देखे हैं, अच्छी खबर है: यदि थ्रेड जीवन के संदर्भ में क्लासोलेडर की अपेक्षा अधिक चल रहा है, तो यह एक शुद्ध अच्छा सा रिसाव है। जब तक वास्तव में ज़रूरत न हो, थ्रेडोकॉकल कैश का उपयोग न करें।

  • कॉलिंग ThreadGroup.destroy()जब थ्रेडग्रुप के पास कोई थ्रेड नहीं है, लेकिन यह अभी भी चाइल्ड थ्रेडग्रुप रखता है। एक बुरा रिसाव जो थ्रेडग्रुप को उसके माता-पिता से दूर करने से रोकेगा, लेकिन सभी बच्चे संयुक्त राष्ट्र के अभिगम्य हो जाएंगे।

  • WeakHashMap और मूल्य (में) का उपयोग करना सीधे कुंजी को संदर्भित करता है। यह ढेर ढेर के बिना खोजने के लिए एक कठिन है। यह उन सभी विस्तारित पर लागू होता है Weak/SoftReferenceजो एक कड़े संदर्भ को संरक्षित ऑब्जेक्ट पर वापस रख सकते हैं।

  • java.net.URLHTTP (एस) प्रोटोकॉल के साथ प्रयोग करना और (!) से संसाधन लोड करना। यह विशेष है, KeepAliveCacheसिस्टम थ्रेडग्रुप में एक नया धागा बनाता है जो वर्तमान थ्रेड के संदर्भ क्लास लोडर को लीक करता है। थ्रेड पहले अनुरोध पर बनाया जाता है जब कोई जीवित धागा मौजूद नहीं होता है, इसलिए या तो आप भाग्यशाली हो सकते हैं या बस लीक हो सकते हैं। रिसाव पहले से ही जावा 7 में तय किया गया है और कोड जो ठीक से थ्रेड बनाता है, संदर्भ क्लास लोडर को हटा देता है। कुछ और मामले हैं (ImageFetcher की तरह, समान धागे बनाने के लिए भी ) तय किया गया

  • कंस्ट्रक्टर ( उदाहरण के लिए) में InflaterInputStreamपासिंग का उपयोग करना और फ़्लोटर का कॉल न करना । ठीक है, अगर आप कंस्ट्रक्टर में बस के साथ गुजरते हैं, तो कोई मौका नहीं ... और हां, स्ट्रीम पर कॉल करने से इन्फ्लोटर बंद नहीं होता है अगर यह मैन्युअली कंस्ट्रक्टर पैरामीटर के रूप में पास हो जाता है। यह एक सही लीक नहीं है क्योंकि यह अंतिम रूप से जारी किया जाएगा ... जब यह आवश्यक हो। उस क्षण तक यह देशी मेमोरी को इतनी बुरी तरह से खा लेता है कि यह Linux oom_killer को इस प्रक्रिया को मारने का कारण बन सकता है। मुख्य मुद्दा यह है कि जावा में अंतिम रूप देना बहुत अविश्वसनीय है और जी 1 ने इसे 7.0.2 तक बदतर बना दिया। कहानी का नैतिक: जितनी जल्दी हो सके देशी संसाधनों को जारी करें; फाइनल बहुत गरीब है।new java.util.zip.Inflater()PNGImageDecoderend()newclose()

  • इसी मामले के साथ java.util.zip.Deflater। यह एक बहुत खराब है क्योंकि डेफ्लिटर जावा में मेमोरी की भूख है, यानी हमेशा 15 बिट्स (अधिकतम) और 8 मेमोरी लेवल (9 अधिकतम) का उपयोग करता है, जो कई सैकड़ों केबी देशी मेमोरी आवंटित करता है। सौभाग्य से, Deflaterव्यापक रूप से उपयोग नहीं किया जाता है और मेरे ज्ञान में JDK का कोई दुरुपयोग नहीं है। हमेशा कॉल करें end()यदि आप मैन्युअल रूप से एक Deflaterया बनाते हैं Inflater। पिछले दो का सबसे अच्छा हिस्सा: आप उन्हें उपलब्ध सामान्य प्रोफाइलिंग टूल के माध्यम से नहीं पा सकते हैं।

(मेरे अनुरोध पर मेरे द्वारा सामना की गई कुछ और समय की आपदाओं को जोड़ सकते हैं।)

खुशकिस्मत रहें और सुरक्षित रहें; लीक बुराई है!


23
Creating but not starting a Thread...Yikes, मैं कुछ सदियों पहले यह एक बुरी तरह से काट लिया गया था! (जावा 1.3)
जूल

@ एलोनबॉयल, इससे पहले कि यह और भी बुरा था क्योंकि धागा सीधे थ्रेडग्रुप में जोड़ दिया गया था, न कि बहुत कठिन रिसाव का मतलब था। यह न केवल unstartedगिनती बढ़ाता है, बल्कि यह थ्रेड ग्रुप को नष्ट करने से रोकता है (कम बुराई लेकिन अभी भी एक रिसाव)
बेस्ट

धन्यवाद! "कॉलिंग ThreadGroup.destroy()जब थ्रेडग्रुप के पास कोई थ्रेड नहीं है ..." एक अविश्वसनीय रूप से सूक्ष्म बग है; मैं घंटों से इसका पीछा कर रहा था, रास्ता भटक गया क्योंकि मेरे नियंत्रण में धागे की गणना करते हुए जीयूआई ने कुछ नहीं दिखाया, लेकिन थ्रेड समूह और, संभवतः, कम से कम एक बच्चा समूह दूर नहीं जाएगा।
लॉरेंस Dol

1
@bestsss: मैं उत्सुक हूं, आप शटडाउन हुक को क्यों निकालना चाहेंगे, यह देखते हुए कि यह अच्छी तरह से चलता है, जेवीएस शटडाउन?
लॉरेंस Dol

203

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

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

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

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

हमारे पास शायद एक जटिल ग्राफ है जो पिछले राज्य को संग्रहीत करता है जिसे गणना द्वारा आवश्यक है। लेकिन पिछला राज्य पहले और इसी तरह राज्य से जुड़ा हुआ है।

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

एक स्मृति रिसाव वास्तव में संभव है और पूरी तरह से अनुमानित है। विशेष भाषा सुविधाओं या कोने के मामलों के लिए कोई ज़रूरत नहीं है। मेमोरी लीक या तो एक संकेतक है कि कुछ गायब है या यहां तक ​​कि डिजाइन की समस्याएं भी हैं।


24
मुझे यह हास्यास्पद लगता है कि अन्य उत्तरों पर लोग उन किनारे के मामलों और चाल की तलाश कर रहे हैं और यह पूरी तरह से बिंदु को याद कर रहे हैं। वे सिर्फ ऐसे कोड दिखा सकते हैं जो उन वस्तुओं के लिए बेकार संदर्भ रखते हैं जो फिर कभी उपयोग नहीं करेंगे, और उन संदर्भों को कभी नहीं हटाएंगे; कोई कह सकता है कि वे मामले "सच" मेमोरी लीक नहीं हैं, क्योंकि अभी भी उन वस्तुओं के आसपास संदर्भ हैं, लेकिन अगर प्रोग्राम कभी भी उन संदर्भों का उपयोग नहीं करता है और उन्हें कभी भी नहीं छोड़ता है, तो यह पूरी तरह से (और जितना बुरा है) एक के बराबर है " सच स्मृति रिसाव "।
एहबाक जूस्त 22'11

@ नाइकोलस बॉस्केट: "मेमोरी लीक वास्तव में बहुत संभव है" बहुत बहुत धन्यवाद। +15 अपवित्र। अच्छा लगा। मैं इस तथ्य को
बताने के

जावा और .NET में GC कुछ अर्थों में है जो वस्तुओं के अनुमान ग्राफ पर पूर्व निर्धारित है जो अन्य वस्तुओं के संदर्भ में है, वस्तुओं के ग्राफ के समान है जो अन्य वस्तुओं के बारे में "देखभाल" करता है। वास्तव में, यह संभव है कि किनारों को संदर्भ ग्राफ में मौजूद किया जा सकता है जो "देखभाल" संबंधों का प्रतिनिधित्व नहीं करते हैं, और किसी वस्तु के लिए किसी अन्य वस्तु के अस्तित्व की परवाह करना संभव है, भले ही कोई प्रत्यक्ष या अप्रत्यक्ष संदर्भ पथ (यहां तक ​​कि उपयोग न करें WeakReference) मौजूद हो एक से दूसरे में। यदि किसी ऑब्जेक्ट रेफरेंस में एक अतिरिक्त बिट था, तो यह "लक्ष्य के बारे में परवाह" संकेतक के लिए सहायक हो सकता है ...
सुपरकैट

... और PhantomReferenceअगर किसी वस्तु के बारे में परवाह नहीं की गई है, तो सिस्टम को सूचनाएं प्रदान करता है (इसी तरह के माध्यम से )। WeakReferenceकुछ हद तक करीब आता है, लेकिन इसे इस्तेमाल करने से पहले एक मजबूत संदर्भ में परिवर्तित किया जाना चाहिए; यदि एक जीसी चक्र होता है जबकि मजबूत संदर्भ मौजूद होता है, तो लक्ष्य को उपयोगी माना जाएगा।
सुपरकैट

यह मेरी राय में सही उत्तर है। हमने सालों पहले एक सिमुलेशन लिखा था। किसी तरह हमने गलती से पिछले राज्य को मेमोरी लीक बनाने की वर्तमान स्थिति से जोड़ा। एक समय सीमा के कारण हमने मेमोरी लीक को कभी हल नहीं किया, लेकिन इसे दस्तावेज बनाकर इसे एक «फीचर» बना दिया।
nalply

163

उत्तर पूरी तरह से इस बात पर निर्भर करता है कि साक्षात्कारकर्ता ने क्या सोचा था कि वे पूछ रहे हैं।

क्या जावा रिसाव करना अभ्यास में संभव है? बेशक यह है, और अन्य उत्तरों में बहुत सारे उदाहरण हैं।

लेकिन कई मेटा-प्रश्न हैं जो पूछे जा रहे हैं?

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

मैं आपके मेटा-प्रश्न को "व्हाट्सएप जवाब दे सकता हूं जो मैं इस साक्षात्कार की स्थिति में उपयोग कर सकता हूं"। और इसलिए, मैं जावा के बजाय साक्षात्कार कौशल पर ध्यान केंद्रित करने जा रहा हूं। मेरा मानना ​​है कि आप एक साक्षात्कार में एक प्रश्न के उत्तर को नहीं जानने की स्थिति को दोहराने की अधिक संभावना रखते हैं, क्योंकि आपको यह जानने की जरूरत है कि जावा रिसाव कैसे किया जाए। तो, उम्मीद है, यह मदद करेगा।

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


22
जब भी मैंने वह प्रश्न पूछा है, मैं एक बहुत ही सरल उत्तर की तलाश कर रहा हूं - एक कतार बढ़ती रहें, अंत में कोई डीबी आदि न हो, विषम कक्षा लोडर / थ्रेड विवरण न हों, इसका मतलब है कि वे समझते हैं कि जीसी आपके लिए क्या कर सकता है और क्या नहीं। मेरे अनुमान के अनुसार आप जिस नौकरी के लिए साक्षात्कार कर रहे हैं, उस पर निर्भर करता है।
डेव जूल

कृपया मेरे प्रश्न पर एक नज़र डालें, धन्यवाद stackoverflow.com/questions/31108772/…
डैनियल न्यूटाउन

130

यदि आप JDBC को नहीं समझते हैं तो निम्न एक बहुत ही व्यर्थ उदाहरण है । या कम से कम कैसे JDBC एक डेवलपर को बंद करने की उम्मीद करता है Connection, Statementऔर ResultSetउन्हें लागू करने से पहले उन्हें त्यागने या बदले में संदर्भ खोने से पहले उदाहरण देता है finalize

void doWork()
{
   try
   {
       Connection conn = ConnectionFactory.getConnection();
       PreparedStatement stmt = conn.preparedStatement("some query"); // executes a valid query
       ResultSet rs = stmt.executeQuery();
       while(rs.hasNext())
       {
          ... process the result set
       }
   }
   catch(SQLException sqlEx)
   {
       log(sqlEx);
   }
}

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

इस तरह की घटना जहां में Connectionकी finalizeविधि नहीं साफ अप सब कुछ करता है, एक वास्तव में मिल सकती है जब तक डेटाबेस सर्वर अंत में पता लगा लेता है कि कनेक्शन जिंदा नहीं है कि डेटाबेस सर्वर से शारीरिक संबंध, चलेगा कई कचरा संग्रहण चक्र (अगर यह करता है), और बंद होना चाहिए।

यहां तक ​​कि अगर JDBC ड्राइवर को लागू करना था finalize, तो अंतिमकरण के दौरान अपवादों को फेंक दिया जाना संभव है। परिणामी व्यवहार यह है कि अब "निष्क्रिय" ऑब्जेक्ट से जुड़ी किसी भी मेमोरी को पुनः प्राप्त नहीं किया जाएगा, जैसा finalizeकि केवल एक बार लागू करने की गारंटी है।

ऑब्जेक्ट फाइनल के दौरान अपवादों का सामना करने का उपरोक्त परिदृश्य एक अन्य अन्य परिदृश्य से संबंधित है जो संभवतः स्मृति रिसाव - ऑब्जेक्ट पुनरुत्थान का कारण बन सकता है। ऑब्जेक्ट पुनरुत्थान को अक्सर जानबूझकर किसी अन्य वस्तु से, अंतिम वस्तु से एक मजबूत संदर्भ बनाकर किया जाता है। जब ऑब्जेक्ट पुनरुत्थान का दुरुपयोग किया जाता है तो यह मेमोरी लीक के अन्य स्रोतों के साथ संयोजन में एक मेमोरी लीक का कारण होगा।

ऐसे और भी बहुत से उदाहरण हैं जिन्हें आप समझ सकते हैं - जैसे

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

98
आम तौर पर मेमोरी की सीमा को हिट करने से पहले आप अधिकतम खुले कनेक्शन की सीमा तक पहुंच जाएंगे। मुझे नहीं पता कि मुझे क्यों पता है ...
हार्डवेयरगुए

Oracle JDBC ड्राइवर ऐसा करने के लिए कुख्यात है।
chotchki

जब तक मैं Connection.closeअपने सभी एसक्यूएल कॉल के अंत में ब्लॉक में डाल दिया, तब तक मैं SQL डेटाबेस की कनेक्शन सीमा को बहुत हिट करता हूं। अतिरिक्त मज़े के लिए मैंने कुछ लंबे समय तक चलने वाली ओरेकल संग्रहीत प्रक्रियाओं को बुलाया जो डेटाबेस पर बहुत अधिक कॉलों को रोकने के लिए जावा पक्ष पर ताले की आवश्यकता थी।
माइकल शोप्सिन

@Hardwareguy यह दिलचस्प है, लेकिन यह वास्तव में आवश्यक नहीं है कि सभी वातावरणों के लिए वास्तविक कनेक्शन सीमाएं प्रभावित होंगी। उदाहरण के लिए, वेबलॉग ऐप सर्वर 11 जी पर तैनात एक एप्लिकेशन के लिए मैंने बड़े पैमाने पर कनेक्शन लीकेज देखे हैं। लेकिन कटाई के एक विकल्प के कारण लीक हुए कनेक्शन डेटाबेस कनेक्शन उपलब्ध थे जबकि मेमोरी लीक को पेश किया जा रहा था। मैं सभी वातावरणों के बारे में निश्चित नहीं हूं।
असेम बंसल

मेरे अनुभव में आपको कनेक्शन बंद करने पर भी स्मृति रिसाव मिलता है। आपको सबसे पहले ResultSet और ReadyedStatement को बंद करना होगा। एक सर्वर था, जो आउटऑफमेरीऑयर्स के कारण घंटों या ठीक काम करने के दिनों के बाद भी बार-बार दुर्घटनाग्रस्त हो गया, जब तक कि मैंने ऐसा करना शुरू नहीं किया।
Bjørn Stenfeldt

119

संभवतः एक संभावित स्मृति रिसाव के सबसे सरल उदाहरणों में से एक है, और इससे कैसे बचा जाए, यह ArrayList.remove (int) का कार्यान्वयन है:

public E remove(int index) {
    RangeCheck(index);

    modCount++;
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index + 1, elementData, index,
                numMoved);
    elementData[--size] = null; // (!) Let gc do its work

    return oldValue;
}

यदि आप इसे स्वयं लागू कर रहे थे, तो क्या आपने सरणी तत्व को साफ़ करने के लिए सोचा होगा जो अब उपयोग नहीं किया जाता है ( elementData[--size] = null)? यह संदर्भ किसी विशाल वस्तु को जीवित रख सकता है ...


5
और स्मृति रिसाव यहाँ कहाँ है?
rds

28
@maniek: मेरा तात्पर्य यह नहीं था कि यह कोड स्मृति रिसाव को प्रदर्शित करता है। मैंने यह बताने के लिए उद्धृत किया कि आकस्मिक वस्तु प्रतिधारण से बचने के लिए कभी-कभी गैर-स्पष्ट कोड की आवश्यकता होती है।
मेरिटॉन

रेंजकैच (सूचकांक) क्या है; ?
तुगे

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

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

68

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


14
मेरा मानना ​​है कि यह "लीक" नहीं है। यह एक बग है, और यह कार्यक्रम और भाषा के डिजाइन से है। एक रिसाव किसी भी संदर्भ के बिना चारों ओर लटकी हुई वस्तु होगी ।
user541686

29
@ मेहरदाद: यह केवल एक संकीर्ण परिभाषा है जो पूरी तरह से सभी भाषाओं पर लागू नहीं होती है। मेरा तर्क है कि कोई भी मेमोरी लीक प्रोग्राम के खराब डिज़ाइन के कारण बग है।
छिपकली

9
@ मेहरदाद: ...then the question of "how do you create a memory leak in X?" becomes meaningless, since it's possible in any language. मैं यह नहीं देखता कि आप उस निष्कर्ष को कैसे खींच रहे हैं। जावा में किसी भी परिभाषा से मेमोरी लीक बनाने के कम तरीके हैं । यह निश्चित रूप से अभी भी एक वैध प्रश्न है।
छिपकली

7
@ 31eee384: यदि आपका प्रोग्राम मेमोरी में ऐसी वस्तुओं को रखता है जिसका वह कभी उपयोग नहीं कर सकता है, तो तकनीकी रूप से यह मेमोरी लीक हो गई है। तथ्य यह है कि आपके पास बड़ी समस्याएं हैं जो वास्तव में नहीं बदलती हैं।
छिपकली

8
@ 31eee384: यदि आप एक तथ्य के लिए जानते हैं कि यह नहीं होगा, तो यह नहीं हो सकता। प्रोग्राम, जैसा कि लिखा गया है, डेटा तक कभी नहीं पहुंचेगा।
छिपकली

51

आप sun.misc.Unsafe वर्ग के साथ मेमोरी लीक करने में सक्षम हैं । वास्तव में इस सेवा वर्ग का उपयोग विभिन्न मानक वर्गों (उदाहरण के लिए java.nio कक्षाओं) में किया जाता है। आप सीधे इस वर्ग का उदाहरण नहीं बना सकते , लेकिन आप ऐसा करने के लिए प्रतिबिंब का उपयोग कर सकते हैं

ग्रहण आईडीई में कोड संकलित नहीं होता है - इसे कमांड का उपयोग करके संकलित करें javac(संकलन के दौरान आपको चेतावनी मिलेगी)

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import sun.misc.Unsafe;


public class TestUnsafe {

    public static void main(String[] args) throws Exception{
        Class unsafeClass = Class.forName("sun.misc.Unsafe");
        Field f = unsafeClass.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
        System.out.print("4..3..2..1...");
        try
        {
            for(;;)
                unsafe.allocateMemory(1024*1024);
        } catch(Error e) {
            System.out.println("Boom :)");
            e.printStackTrace();
        }
    }

}

1
आवंटित स्मृति कचरा संग्रहकर्ता के लिए अदृश्य है
स्टेम जूल 11'11

4
आवंटित मेमोरी जावा से संबंधित नहीं है।
बेस्ट

क्या यह सूर्य / ओरेकल jvm विशिष्ट है? ईजी आईबीएम पर यह काम करेगा?
बर्लिन ब्राउन

2
स्मृति निश्चित रूप से "जावा से संबंधित है", कम से कम इस अर्थ में कि मैं) किसी और के लिए उपलब्ध नहीं है, ii) जब जावा अनुप्रयोग बाहर निकलता है तो इसे सिस्टम में वापस कर दिया जाएगा। यह जेवीएम के ठीक बाहर है।
माइकल एंडरसन

3
यह ग्रहण में निर्मित होगा (कम से कम हाल के संस्करणों में) लेकिन आपको संकलक सेटिंग्स बदलने की आवश्यकता होगी: विंडो में> वरीयताएँ> जावा> कंपाइलर> त्रुटियां / चेतावनी> डिप्रेस्ड और प्रतिबंधित एपीआई सेट निषिद्ध संदर्भ (एक्सेस नियम) "चेतावनी "।
माइकल एंडरसन

43

मैं अपने उत्तर को यहाँ से कॉपी कर सकता हूँ: जावा में मेमोरी रिसाव का सबसे आसान तरीका?

"कंप्यूटर विज्ञान (या इस संदर्भ में रिसाव) में एक मेमोरी लीक, तब होता है जब एक कंप्यूटर प्रोग्राम मेमोरी की खपत करता है, लेकिन इसे ऑपरेटिंग सिस्टम पर वापस जारी करने में असमर्थ होता है।" (विकिपीडिया)

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

अब तक के सभी अन्य जवाब मेरी परिभाषा में हैं कि वास्तव में मेमोरी लीक नहीं है। वे सभी व्यर्थ सामान को असली तेजी से मेमोरी को भरने का लक्ष्य रखते हैं। लेकिन किसी भी समय आप अभी भी आपके द्वारा बनाई गई वस्तुओं को निष्क्रिय कर सकते हैं और इस तरह मेमोरी को मुक्त कर सकते हैं -> NO LEAK। acconrad का जवाब बहुत करीब आता है, क्योंकि मुझे स्वीकार करना होगा क्योंकि उसका समाधान प्रभावी ढंग से कचरा कलेक्टर को "एक अंतहीन लूप में मजबूर करके" क्रैश करना है)।

लंबा उत्तर है: आप जेएनआई का उपयोग करके जावा के लिए एक पुस्तकालय लिखकर मेमोरी लीक प्राप्त कर सकते हैं, जिसमें मैनुअल मेमोरी प्रबंधन हो सकता है और इस प्रकार मेमोरी लीक हो सकती है। यदि आप इस लाइब्रेरी को कहते हैं, तो आपकी जावा प्रक्रिया मेमोरी को लीक कर देगी। या, आप JVM में बग हो सकते हैं, ताकि JVM मेमोरी खो दे। जेवीएम में संभवतः कीड़े हैं, यहां तक ​​कि कुछ ज्ञात भी हो सकते हैं क्योंकि कचरा संग्रह उस तुच्छ नहीं है, लेकिन फिर भी यह एक बग है। डिजाइन से यह संभव नहीं है। आप इस तरह के बग से प्रभावित होने वाले कुछ जावा कोड के लिए पूछ सकते हैं। क्षमा करें, मैं एक नहीं जानता और यह अगले जावा संस्करण में वैसे भी अब बग नहीं हो सकता है।


12
यह स्मृति लीक की एक अत्यंत सीमित (और बहुत उपयोगी नहीं) परिभाषा है। व्यावहारिक उद्देश्यों के लिए समझ में आने वाली एकमात्र परिभाषा "एक स्मृति रिसाव किसी भी स्थिति में है जिसमें डेटा को रखने के बाद प्रोग्राम को स्मृति को आवंटित करना जारी रहता है, जिसकी अब आवश्यकता नहीं है।"
मेसन व्हीलर

1
उल्लिखित एक्सॉन के जवाब को हटा दिया गया था?
टॉम ज़ातो -

1
@ TomáZZato: नहीं, यह नहीं है। मैंने अब लिंक करने के लिए उपरोक्त संदर्भ को बदल दिया, ताकि आप इसे आसानी से पा सकें।
यांकी

वस्तु पुनरुत्थान क्या है? एक विध्वंसक कितनी बार कहा जाता है? ये प्रश्न इस उत्तर को कैसे नापसंद करते हैं?
ऑटिस्टिक

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

39

यहाँ http://wiki.eclipse.org/Performance_Bloopers#String.substring.28.29 के माध्यम से एक सरल / भयावह है ।

public class StringLeaker
{
    private final String muchSmallerString;

    public StringLeaker()
    {
        // Imagine the whole Declaration of Independence here
        String veryLongString = "We hold these truths to be self-evident...";

        // The substring here maintains a reference to the internal char[]
        // representation of the original string.
        this.muchSmallerString = veryLongString.substring(0, 1);
    }
}

क्योंकि प्रतिस्थापन मूल के आंतरिक प्रतिनिधित्व को संदर्भित करता है, बहुत लंबा स्ट्रिंग, मूल स्मृति में रहता है। इस प्रकार, जब तक आपके पास नाटक में एक स्ट्रिंगर है, तब तक आपके पास मेमोरी में पूरे मूल स्ट्रिंग हैं, भले ही आपको लगता है कि आप केवल एकल-वर्ण स्ट्रिंग पर पकड़ रहे हैं।

मूल स्ट्रिंग के अवांछित संदर्भ को संग्रहीत करने से बचने का तरीका कुछ इस तरह है:

...
this.muchSmallerString = new String(veryLongString.substring(0, 1));
...

अतिरिक्त खराब होने के लिए, आप प्रतिस्थापन भी कर सकते हैं .intern():

...
this.muchSmallerString = veryLongString.substring(0, 1).intern();
...

ऐसा करने से स्ट्रींगलेकर के उदाहरण को छोड़ दिए जाने के बाद भी मूल लंबे स्ट्रिंग और मेमोरी में व्युत्पन्न दोनों को रखा जाएगा।


4
मैं नहीं कहूंगा कि एक मेमोरी लीक, प्रति से । जब मुक्त किया muchSmallerStringजाता है (क्योंकि StringLeakerवस्तु नष्ट हो जाती है), लंबी स्ट्रिंग को भी मुक्त किया जाएगा। जिसे मैं मेमोरी लीक कहता हूं वह मेमोरी है जिसे जेवीएम के इस उदाहरण में कभी भी मुक्त नहीं किया जा सकता है। हालाँकि, आपने स्वयं को दिखाया है कि मेमोरी को कैसे खाली किया जाए this.muchSmallerString=new String(this.muchSmallerString):। एक वास्तविक मेमोरी लीक के साथ, कुछ भी नहीं है जो आप कर सकते हैं।
आरडीएस

2
@, यह एक उचित बिंदु है। गैर- internमामला "स्मृति रिसाव" की तुलना में "मेमोरी आश्चर्य" का अधिक हो सकता है। .intern()हालांकि, सब्रिंग आईएनजी, निश्चित रूप से एक ऐसी स्थिति बनाता है जहां लंबी स्ट्रिंग का संदर्भ संरक्षित है और इसे मुक्त नहीं किया जा सकता है।
जॉन चैंबर्स

15
विधि विकल्प () java7 में एक नया स्ट्रिंग बनाता है (यह एक नया व्यवहार है)
anstarovoyt

आपको सबस्ट्रिंग () स्वयं करने की भी आवश्यकता नहीं है: एक विशाल इनपुट के एक छोटे से हिस्से से मिलान करने के लिए एक रेगेक्स मैचर का उपयोग करें, और लंबे समय तक "निकाले गए" स्ट्रिंग को साथ रखें। विशाल इनपुट जावा 6.
Bananeweizen

37

GUI कोड में इसका एक सामान्य उदाहरण एक विजेट / कंपोनेंट बनाते समय और कुछ स्टैटिक / एप्लिकेशन स्कोप्ड ऑब्जेक्ट में एक श्रोता को जोड़ने और फिर विजेट के नष्ट होने पर श्रोता को नहीं हटाने का है। न केवल आपको एक स्मृति रिसाव मिलता है, बल्कि एक प्रदर्शन भी हिट होता है जब आप जो भी आग की घटनाओं को सुन रहे होते हैं, आपके सभी पुराने श्रोताओं को भी बुलाया जाता है।


1
एंड्रॉइड प्लेटफॉर्म एक दृश्य के स्थिर क्षेत्र में बिटमैप को कैशिंग करके बनाई गई मेमोरी लीक की छूट देता है ।
आरडीएस

36

किसी भी सर्वलेट कंटेनर (टॉमकैट, जेट्टी, ग्लासफिश, जो भी ...) में चल रहे किसी भी वेब एप्लिकेशन को लें। एप्लिकेशन को पंक्ति में 10 या 20 बार रिडिप्‍लाइ करें (यह सर्वर के ऑटोडेप्‍लॉय डायरेक्टरी में केवल WAR को छूने के लिए पर्याप्‍त हो सकता है।

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

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

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

आपके एप्लिकेशन द्वारा शुरू किए गए थ्रेड्स, थ्रेडलोकल वैरिएबल, लॉगिंग एपेंडर्स क्लासॉकर लीक का कारण बनने वाले कुछ सामान्य संदिग्ध हैं।


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

35

शायद जेएनआई के माध्यम से बाहरी देशी कोड का उपयोग करके?

शुद्ध जावा के साथ, यह लगभग असंभव है।

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


22
यह "मेमोरी लीक" की परिभाषा पर निर्भर करता है। यदि "मेमोरी जो कि चालू है, लेकिन अब ज़रूरत नहीं है", तो जावा में करना आसान है। यदि यह "मेमोरी जो आवंटित की गई है लेकिन कोड द्वारा बिल्कुल भी सुलभ नहीं है", तो यह थोड़ा कठिन हो जाता है।
जोकिम सॉयर

@ जोकिम सौर - मेरा मतलब दूसरे प्रकार का था। पहला बनाने में काफी आसान है :)
रोगैक

6
"शुद्ध जावा के साथ, यह लगभग असंभव है।" खैर, मेरा अनुभव एक और है, खासकर जब यह उन लोगों द्वारा कैश लागू करने की बात आती है जो यहां के नुकसान के बारे में नहीं जानते हैं।
फेबियन बार्नी

4
@ रॉग: मूल रूप से +10 000 लोगों के विभिन्न उत्तरों पर मूल रूप से 400 upvotes हैं, जो दिखा रहे हैं कि दोनों मामलों में जोआचिम सॉर ने टिप्पणी की कि यह बहुत संभव है। तो आपका "लगभग असंभव" कोई मतलब नहीं है।
SyntaxT3rr0r

32

एक बार पर्मगेन और एक्सएमएल पार्सिंग के संबंध में मेरे पास एक अच्छा "मेमोरी लीक" है। XML पार्सर जिसका हमने उपयोग किया (मुझे याद नहीं है कि यह कौन सा था) टैग नामों पर एक String.intern () किया, जिससे तुलना तेजी से हो सके। हमारे ग्राहकों में से एक को XML विशेषताओं या पाठ में डेटा मानों को संग्रहीत करने का बहुत अच्छा विचार था, लेकिन tagnames के रूप में, इसलिए हमारे पास एक दस्तावेज़ था:

<data>
   <1>bla</1>
   <2>foo</>
   ...
</data>

वास्तव में, वे संख्याओं का उपयोग नहीं करते थे, लेकिन लंबे समय तक पाठ्य आईडी (लगभग 20 अक्षर), जो अद्वितीय थे और प्रतिदिन 10-15 मिलियन की दर से आते थे। यह एक दिन में 200 एमबी बकवास बनाता है, जिसे फिर कभी ज़रूरत नहीं होती है, और कभी भी जीसीड (क्योंकि यह पर्मगेन में नहीं है)। हमारे पास 512 एमबी की अनुमति थी, इसलिए आउट-ऑफ-मेमोरी अपवाद (OOME) आने में दो दिन लग गए ...


4
बस अपने उदाहरण कोड को नाइटपिक करने के लिए: मुझे लगता है कि संख्याएँ (या संख्याओं से शुरू होने वाले तार) को XML में तत्व नामों के रूप में अनुमति नहीं है।
पाओलो एबरमन

ध्यान दें कि यह अब JDK 7+ के लिए सही नहीं है, जहां स्ट्रिंग इंटर्निंग ढेर पर होता है। : एक विस्तृत writeup के लिए यह लेख देखें java-performance.info/string-intern-in-java-6-7-8
jmiserez

तो, मुझे लगता है कि स्ट्रिंग के स्थान पर स्ट्रिंगर का उपयोग करने से यह समस्या दूर हो जाएगी? यह नहीं होगा?
शुभ

24

स्मृति रिसाव क्या है:

  • यह बग या खराब डिज़ाइन के कारण होता है
  • यह स्मृति की बर्बादी है।
  • यह समय के साथ खराब हो जाता है।
  • कचरा उठाने वाला इसे साफ नहीं कर सकता।

विशिष्ट उदाहरण:

वस्तुओं का एक कैश चीजों को गड़बड़ाने का एक अच्छा प्रारंभिक बिंदु है।

private static final Map<String, Info> myCache = new HashMap<>();

public void getInfo(String key)
{
    // uses cache
    Info info = myCache.get(key);
    if (info != null) return info;

    // if it's not in cache, then fetch it from the database
    info = Database.fetch(key);
    if (info == null) return null;

    // and store it in the cache
    myCache.put(key, info);
    return info;
}

आपका कैश बढ़ता और बढ़ता है। और बहुत जल्द पूरे डेटाबेस को स्मृति में चूसा जाता है। एक बेहतर डिज़ाइन एक LRUMap (केवल हाल ही में उपयोग की गई वस्तुओं को कैश में रखता है) का उपयोग करता है।

ज़रूर, आप चीजों को और अधिक जटिल बना सकते हैं:

  • थ्रेडलोक निर्माण का उपयोग करना ।
  • अधिक जटिल संदर्भ पेड़ जोड़ना ।
  • या 3 पार्टी पुस्तकालयों के कारण लीक ।

अक्सर क्या होता है:

यदि इस Info ऑब्जेक्ट में अन्य ऑब्जेक्ट्स के संदर्भ हैं, जिसमें फिर से अन्य ऑब्जेक्ट्स के संदर्भ हैं। एक तरह से आप इसे किसी प्रकार की मेमोरी लीक (खराब डिज़ाइन के कारण) भी मान सकते हैं।


22

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

public class Example1 {
  public Example2 getNewExample2() {
    return this.new Example2();
  }
  public class Example2 {
    public Example2() {}
  }
}

अब यदि आप उदाहरण 1 को कॉल करते हैं और एक उदाहरण 2 को छोड़ते हुए उदाहरण 1 प्राप्त करते हैं, तो आप स्वाभाविक रूप से अभी भी एक उदाहरण 1 वस्तु का लिंक देंगे।

public class Referencer {
  public static Example2 GetAnExample2() {
    Example1 ex = new Example1();
    return ex.getNewExample2();
  }

  public static void main(String[] args) {
    Example2 ex = Referencer.GetAnExample2();
    // As long as ex is reachable; Example1 will always remain in memory.
  }
}

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


2
आंतरिक कक्षाएं शायद ही कभी एक मुद्दा होती हैं। वे एक सीधा मामला है और पता लगाने के लिए बहुत आसान है। अफवाह सिर्फ एक अफवाह भी है।
बेस्ट

2
"अफवाह" किसी को अर्ध-पढ़ने की तरह लगता है कि कैसे पीढ़ीगत जीसी काम करता है। लंबे समय तक जीवित-लेकिन-अब-अगम्य वस्तुएं वास्तव में चारों ओर चिपक सकती हैं और थोड़ी देर के लिए जगह ले सकती हैं, क्योंकि जेवीएम ने उन्हें युवा पीढ़ियों से बाहर कर दिया, ताकि यह हर पास की जांच करना बंद कर सके। वे डिजाइन के द्वारा "मेरे 5000 टेम्प स्ट्रिंग्स को साफ़ करें" पास को खाली कर देंगे। लेकिन वे अमर नहीं हैं। वे अभी भी संग्रह के लिए पात्र हैं, और यदि VM RAM के लिए स्ट्रैप्ड है, तो यह अंततः एक पूर्ण GC स्वीप चलाएगा और उस मेमोरी को रिपॉजिट करेगा।
cHao

22

मुझे हाल ही में log4j द्वारा एक तरह से मेमोरी लीक की स्थिति का सामना करना पड़ा।

Log4j में नेस्टेड डायग्नोस्टिक कॉन्सेप्ट (NDC) नामक यह तंत्र है जो विभिन्न स्रोतों से लॉग आउटपुट को अलग करने के लिए एक उपकरण है। जिस NDR पर काम करता है, उसकी ग्रैन्युलैरिटी थ्रेड्स है, इसलिए यह अलग-अलग थ्रेड्स से लॉग आउटपुट को अलग-अलग करता है।

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

pubclic class RequestProcessor {
    private static final Logger logger = Logger.getLogger(RequestProcessor.class);
    public void doSomething()  {
        ....
        final List<String> hugeList = new ArrayList<String>(10000);
        new Thread() {
           public void run() {
               logger.info("Child thread spawned")
               for(String s:hugeList) {
                   ....
               }
           }
        }.start();
    }
}    

तो एक एनडीसी संदर्भ इनलाइन थ्रेड के साथ जुड़ा हुआ था जिसे स्पॉन किया गया था। थ्रेड ऑब्जेक्ट जो इस NDC संदर्भ के लिए महत्वपूर्ण था, एक इनलाइन थ्रेड है, जिसमें भारी-भरकम ऑब्जेक्ट लटका हुआ है। इसलिए थ्रेड ने जो किया उसे पूरा करने के बाद भी, एनडीसी के संदर्भ Hastable द्वारा विशालवाद के संदर्भ को जीवित रखा गया, जिससे स्मृति रिसाव हो गया।


वह चूसता है। आपको इस लॉगिंग लाइब्रेरी की जांच करनी चाहिए जो किसी फ़ाइल में लॉग इन करते समय शून्य मेमोरी आवंटित करती है: mentalog.soliveirajr.com
TraderJoeChicago

+1 क्या आप जानते हैं कि slf4j / logback (एक ही लेखक द्वारा उत्तराधिकारी उत्पादों) में MDC के साथ एक समान मुद्दा है? मैं स्रोत पर एक गहरा गोता लगाने वाला हूं, लेकिन पहले जांच करना चाहता था। किसी भी तरह से, यह पोस्ट करने के लिए धन्यवाद।
sparc_spread

20

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

class A {
    B bRef;
}

class B {
    A aRef;
}

public class Main {
    public static void main(String args[]) {
        A myA = new A();
        B myB = new B();
        myA.bRef = myB;
        myB.aRef = myA;
        myA=null;
        myB=null;
        /* at this point, there is no access to the myA and myB objects, */
        /* even though both objects still have active references. */
    } /* main */
}

फिर आप यह समझा सकते हैं कि संदर्भ गणना के साथ, उपरोक्त कोड मेमोरी को लीक करेगा। लेकिन अधिकांश आधुनिक जेवीएम संदर्भ गणना का उपयोग नहीं करते हैं, अधिकांश स्वीप कचरा संग्रहकर्ता का उपयोग करते हैं, जो वास्तव में इस मेमोरी को इकट्ठा करेगा।

आगे आप एक वस्तु बनाने की व्याख्या कर सकते हैं जिसमें एक अंतर्निहित मूल संसाधन है, जैसे:

public class Main {
    public static void main(String args[]) {
        Socket s = new Socket(InetAddress.getByName("google.com"),80);
        s=null;
        /* at this point, because you didn't close the socket properly, */
        /* you have a leak of a native descriptor, which uses memory. */
    }
}

फिर आप यह समझा सकते हैं कि यह तकनीकी रूप से एक मेमोरी लीक है, लेकिन वास्तव में लीक जेवीएम में मूल कोड के कारण होता है जो अंतर्निहित मूल संसाधनों को आवंटित करता है, जिन्हें आपके जावा कोड द्वारा मुक्त नहीं किया गया था।

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


19

हर कोई हमेशा मूल कोड मार्ग भूल जाता है। यहाँ एक रिसाव के लिए सरल सूत्र है:

  1. देशी विधि घोषित करें।
  2. देशी तरीके से, बुलाओ malloc। फोन मत करोfree
  3. देशी विधि को बुलाओ।

याद रखें, देशी कोड में मेमोरी आवंटन जेवीएम हीप से आते हैं।


1
एक सच्ची कहानी पर आधारित।
रेग

18

एक स्थैतिक मानचित्र बनाएं और इसमें कठिन संदर्भ जोड़ते रहें। वे कभी GC'd नहीं होंगे।

public class Leaker {
    private static final Map<String, Object> CACHE = new HashMap<String, Object>();

    // Keep adding until failure.
    public static void addToCache(String key, Object value) { Leaker.CACHE.put(key, value); }
}

87
यह एक रिसाव कैसे है? यह वही कर रहा है जो आप इसे करने के लिए कह रहे हैं। यदि वह रिसाव है, तो वस्तुओं को कहीं भी बनाना और संग्रहीत करना एक रिसाव है।
फल्मरी जूल 21'11

3
मैं @Falmarri से सहमत हूं। मुझे वहां कोई रिसाव नहीं दिख रहा है, आप सिर्फ ऑब्जेक्ट बना रहे हैं। आप निश्चित रूप से उस मेमोरी को 'पुनः प्राप्त' कर सकते हैं जिसे आपने 'रिमूफ़फ्रैचे' नामक एक अन्य विधि के साथ आवंटित किया था। एक रिसाव है जब आप मेमोरी को पुनः प्राप्त नहीं कर सकते।
काइल

3
मेरा कहना है कि कोई व्यक्ति जो वस्तुओं का निर्माण करता रहता है, शायद उन्हें कैश में डाल देता है, अगर वे सावधान नहीं हैं तो एक OOM त्रुटि के साथ समाप्त हो सकता है।
duffymo

8
@duffymo: लेकिन यह वास्तव में सवाल क्या पूछ रहा था नहीं है। इसका सिर्फ आपकी सभी मेमोरी का उपयोग करने से कोई लेना-देना नहीं है।
फल्मरी जूल

3
बिल्कुल अमान्य है। आप केवल मानचित्र संग्रह में वस्तुओं का एक समूह एकत्र कर रहे हैं। उनका संदर्भ रखा जाएगा क्योंकि मानचित्र उन्हें रखता है।
गोर्यग्राभम

16

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

class Leakee {
    public void check() {
        if (depth > 2) {
            Leaker.done();
        }
    }
    private int depth;
    public Leakee(int d) {
        depth = d;
    }
    protected void finalize() {
        new Leakee(depth + 1).check();
        new Leakee(depth + 1).check();
    }
}

public class Leaker {
    private static boolean makeMore = true;
    public static void done() {
        makeMore = false;
    }
    public static void main(String[] args) throws InterruptedException {
        // make a bunch of them until the garbage collector gets active
        while (makeMore) {
            new Leakee(0).check();
        }
        // sit back and watch the finalizers chew through memory
        while (true) {
            Thread.sleep(1000);
            System.out.println("memory=" +
                    Runtime.getRuntime().freeMemory() + " / " +
                    Runtime.getRuntime().totalMemory());
        }
    }
}

15

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


10
यह असत्य है। finalize()नहीं बुलाया जाएगा, लेकिन एक बार और अधिक संदर्भ नहीं होगा, तो वस्तु को एकत्र किया जाएगा। कचरा बीनने वाले को 'कहा नहीं' जाता है।
बेस्ट जूल 5'11

1
यह उत्तर भ्रामक है, finalize()विधि को केवल एक बार जेवीएम द्वारा बुलाया जा सकता है, लेकिन इसका मतलब यह नहीं है कि वस्तु को फिर से जीवित करने और फिर से डीएरफेर किए जाने पर इसे फिर से इकट्ठा नहीं किया जा सकता है। यदि finalize()विधि में संसाधन समापन कोड है तो यह कोड फिर से नहीं चलेगा, इससे मेमोरी रिसाव हो सकता है।
टॉम कैममैन

15

मैं हाल ही में एक अधिक सूक्ष्म प्रकार के रिसोर्स लीक पर आया था। हम क्लास लोडर के getResourceAsStream के माध्यम से संसाधन खोलते हैं और ऐसा हुआ कि इनपुट स्ट्रीम हैंडल बंद नहीं हुए।

उम्म, आप कह सकते हैं, क्या बेवकूफ है।

खैर, यह क्या दिलचस्प बनाता है: इस तरह, आप JVM के हीप के बजाय अंतर्निहित प्रक्रिया की हीप मेमोरी को लीक कर सकते हैं।

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

आप निम्न वर्ग के साथ आसानी से ऐसा जार बना सकते हैं:

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class BigJarCreator {
    public static void main(String[] args) throws IOException {
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File("big.jar")));
        zos.putNextEntry(new ZipEntry("resource.txt"));
        zos.write("not too much in here".getBytes());
        zos.closeEntry();
        zos.putNextEntry(new ZipEntry("largeFile.out"));
        for (int i=0 ; i<10000000 ; i++) {
            zos.write((int) (Math.round(Math.random()*100)+20));
        }
        zos.closeEntry();
        zos.close();
    }
}

बस BigJarCreator.java नामक फ़ाइल में पेस्ट करें, इसे कमांड लाइन से संकलित करें और चलाएं:

javac BigJarCreator.java
java -cp . BigJarCreator

Et voilà: आपको अपनी वर्तमान वर्किंग डायरेक्टरी में एक जार आर्काइव मिल जाता है जिसमें दो फाइलें होती हैं।

आइए दूसरी कक्षा बनाएँ:

public class MemLeak {
    public static void main(String[] args) throws InterruptedException {
        int ITERATIONS=100000;
        for (int i=0 ; i<ITERATIONS ; i++) {
            MemLeak.class.getClassLoader().getResourceAsStream("resource.txt");
        }
        System.out.println("finished creation of streams, now waiting to be killed");

        Thread.sleep(Long.MAX_VALUE);
    }

}

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

यदि आपको संदेह है, तो ऊपर की कक्षा को संकलित करने और शुरू करने का प्रयास करें, लेकिन एक सभ्य ढेर आकार (2 एमबी) चुनना सुनिश्चित करें:

javac MemLeak.java
java -Xmx2m -classpath .:big.jar MemLeak

आप यहाँ एक OOM त्रुटि का सामना नहीं करेंगे, क्योंकि कोई संदर्भ नहीं रखा गया है, तो आवेदन ऊपर दिए गए उदाहरण में आपके द्वारा चुना गया कोई फर्क नहीं पड़ता है कि आपने कितने बड़े बदलाव किए हैं। आपकी प्रक्रिया की मेमोरी खपत (शीर्ष (आरईएस / आरएसएस) या प्रक्रिया एक्सप्लोरर में दिखाई देती है) तब तक बढ़ती है जब तक कि आवेदन प्रतीक्षा कमांड को नहीं मिलता है। ऊपर के सेटअप में, यह लगभग 150 एमबी मेमोरी में आवंटित करेगा।

यदि आप चाहते हैं कि एप्लिकेशन सुरक्षित रूप से चले, तो इनपुट स्ट्रीम को वहीं बंद करें जहां यह बनाया गया है:

MemLeak.class.getClassLoader().getResourceAsStream("resource.txt").close();

और आपकी प्रक्रिया 35 एमबी से अधिक नहीं होगी, जो पुनरावृत्ति गणना से स्वतंत्र है।

काफी सरल और आश्चर्यजनक।


14

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

उन वस्तुओं को बनाने के विचार जिनके पास बहुत बड़ा पदचिह्न है और फिर उन्हें एक्सेस नहीं कर पा रहे हैं वे वास्तविक मेमोरी लीक भी नहीं हैं। यदि कुछ भी इसे एक्सेस नहीं कर सकता है, तो यह कचरा एकत्र किया जाएगा, और अगर कुछ इसे एक्सेस कर सकता है तो यह रिसाव नहीं है ...

एक तरीका यह है कि इस्तेमाल किया काम हालांकि करने के लिए - और अगर यह अभी भी होता है मैं नहीं जानता कि - एक तीन गहरी परिपत्र श्रृंखला है। जैसे ऑब्जेक्ट ए में ऑब्जेक्ट बी का संदर्भ है, ऑब्जेक्ट बी में ऑब्जेक्ट सी का संदर्भ है और ऑब्जेक्ट सी में ऑब्जेक्ट ए का संदर्भ है। जीसी यह जानने के लिए पर्याप्त चतुर था कि दो गहरी श्रृंखला - जैसे कि ए <-> बी में है। - ए और बी को किसी और चीज द्वारा सुलभ नहीं होने पर सुरक्षित रूप से एकत्र किया जा सकता है, लेकिन तीन-तरफ़ा श्रृंखला को संभाल नहीं सकता है ...


7
पिछले कुछ समय से ऐसा नहीं है। आधुनिक जीसी जानते हैं कि परिपत्र संदर्भ कैसे संभालना है।
अस्मिता

13

संभावित रूप से विशाल मेमोरी लीक बनाने का एक और तरीका है Map.Entry<K,V>, एक के संदर्भों को पकड़नाTreeMap

यह अनुमान लगाना कठिन है कि यह केवल TreeMapएस पर क्यों लागू होता है , लेकिन कार्यान्वयन को देखकर कारण यह हो सकता है कि: एTreeMap.Entry स्टोर अपने भाई-बहनों के संदर्भ में है, इसलिए यदि कोई TreeMapएकत्र होने के लिए तैयार है, लेकिन कुछ अन्य वर्ग किसी भी संदर्भ का संदर्भ लेते हैं इसका Map.Entry, फिर पूरे मानचित्र को स्मृति में रखा जाएगा।


वास्तविक जीवन परिदृश्य:

एक बड़ी TreeMapडेटा संरचना को लौटाने वाली db क्वेरी की कल्पना करें । TreeMapतत्व सम्मिलन क्रम को बनाए रखने के लिए लोग आमतौर पर एस का उपयोग करते हैं ।

public static Map<String, Integer> pseudoQueryDatabase();

यदि क्वेरी को बहुत बार कहा जाता था और, प्रत्येक क्वेरी के लिए (इसलिए, प्रत्येक Mapलौटे के लिए) आप एक को बचाते हैंEntry कहीं , तो मेमोरी लगातार बढ़ती रहेगी।

निम्नलिखित आवरण वर्ग पर विचार करें:

class EntryHolder {
    Map.Entry<String, Integer> entry;

    EntryHolder(Map.Entry<String, Integer> entry) {
        this.entry = entry;
    }
}

आवेदन:

public class LeakTest {

    private final List<EntryHolder> holdersCache = new ArrayList<>();
    private static final int MAP_SIZE = 100_000;

    public void run() {
        // create 500 entries each holding a reference to an Entry of a TreeMap
        IntStream.range(0, 500).forEach(value -> {
            // create map
            final Map<String, Integer> map = pseudoQueryDatabase();

            final int index = new Random().nextInt(MAP_SIZE);

            // get random entry from map
            for (Map.Entry<String, Integer> entry : map.entrySet()) {
                if (entry.getValue().equals(index)) {
                    holdersCache.add(new EntryHolder(entry));
                    break;
                }
            }
            // to observe behavior in visualvm
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

    }

    public static Map<String, Integer> pseudoQueryDatabase() {
        final Map<String, Integer> map = new TreeMap<>();
        IntStream.range(0, MAP_SIZE).forEach(i -> map.put(String.valueOf(i), i));
        return map;
    }

    public static void main(String[] args) throws Exception {
        new LeakTest().run();
    }
}

प्रत्येक pseudoQueryDatabase()कॉल के बाद ,map इंस्टेंस संग्रह के लिए तैयार होना चाहिए, लेकिन ऐसा नहीं होगा, क्योंकि कम से कम एक Entryऔर कहीं संग्रहीत किया जाता है।

तुम पर निर्भर jvm सेटिंग के , एप्लिकेशन प्रारंभिक चरण में क्रैश हो सकता है OutOfMemoryError

आप इस visualvmग्राफ से देख सकते हैं कि कैसे मेमोरी बढ़ती रहती है।

मेमोरी डंप - ट्रीपैप

एक हैशेड डेटा-संरचना के साथ ऐसा नहीं होता है (HashMap ) के ।

यह एक का उपयोग करते समय ग्राफ है HashMap

मेमोरी डंप - HashMap

समाधान? बस सीधे कुंजी / मूल्य (जैसा कि आप शायद पहले से ही करते हैं) को बचाने के बजाय बचाएंMap.Entry


मैंने यहां अधिक व्यापक बेंचमार्क लिखा है


11

समाप्त होने तक धागे एकत्र नहीं किए जाते हैं। वे कचरा संग्रहण की जड़ों के रूप में काम करते हैं । वे उन कुछ वस्तुओं में से एक हैं जिन्हें केवल उनके बारे में भूलकर या उनके संदर्भों को साफ़ करके पुनः प्राप्त नहीं किया जाएगा।

विचार करें: एक श्रमिक धागे को समाप्त करने का मूल पैटर्न थ्रेड द्वारा देखे गए कुछ स्थिति चर को सेट करना है। थ्रेड समय-समय पर चर की जांच कर सकता है और इसे समाप्त करने के लिए एक संकेत के रूप में उपयोग कर सकता है। यदि चर घोषित नहीं किया गया हैvolatile , तो चर के परिवर्तन को थ्रेड द्वारा नहीं देखा जा सकता है, इसलिए इसे समाप्त करने का पता नहीं चलेगा। या कल्पना करें कि कुछ धागे एक साझा वस्तु को अपडेट करना चाहते हैं, लेकिन उस पर लॉक करने की कोशिश करते समय गतिरोध।

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

एक खिलौना उदाहरण के रूप में:

static void leakMe(final Object object) {
    new Thread() {
        public void run() {
            Object o = object;
            for (;;) {
                try {
                    sleep(Long.MAX_VALUE);
                } catch (InterruptedException e) {}
            }
        }
    }.start();
}

System.gc()आप सभी को कॉल करें , लेकिन ऑब्जेक्ट पास हो गयाleakMe मर जाते हैं कभी नहीं होगा।

(* संपादित *)


1
@Spidey कुछ भी "अटक" नहीं है। कॉलिंग विधि तुरंत लौटती है, और पारित वस्तु को फिर से प्राप्त नहीं किया जाएगा। यह ठीक एक रिसाव है।
Boan

1
आपके पास अपने कार्यक्रम के जीवनकाल के लिए एक धागा "रनिंग" (या सोना, जो भी हो) होगा। यह मेरे लिए एक रिसाव के रूप में नहीं गिना जाता है। साथ ही एक पूल एक रिसाव के रूप में गिनती नहीं करता है, भले ही आप इसे पूरी तरह से उपयोग न करें।
स्पाइडी

1
@Spidey "आपके पास अपने कार्यक्रम के जीवनकाल के लिए एक [चीज़] होगी। यह मेरे लिए लीक के रूप में नहीं गिना जाता है।" क्या आप खुद सुनते हैं?
बोआन

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

1
लीक की व्यावहारिक परिभाषा यह है कि यह स्मृति है जो इस तरह से खो गई है कि हम नहीं जानते हैं और इस तरह इसे पुनः प्राप्त करने के लिए आवश्यक प्रक्रिया नहीं कर सकते हैं; हमें पूरी मेमोरी स्पेस को फाड़ना और पुनर्निर्माण करना होगा। इस तरह एक दुष्ट धागा स्वाभाविक रूप से गतिरोध या डोडी थ्रेडपूल कार्यान्वयन के माध्यम से उत्पन्न हो सकता है। परोक्ष रूप से, ऐसे धागों द्वारा संदर्भित वस्तुओं को अब कभी एकत्र होने से रोका जाता है, इसलिए हमारे पास स्मृति है जो कार्यक्रम के जीवनकाल के दौरान स्वाभाविक रूप से पुनः प्राप्त या पुन: प्रयोज्य नहीं होगी। मैं कहता हूँ कि एक समस्या; विशेष रूप से यह एक स्मृति रिसाव है।
Boann

10

मुझे लगता है कि एक वैध उदाहरण थ्रेडलोकल वैरिएबल का उपयोग ऐसे वातावरण में किया जा सकता है जहां थ्रेड पूल किए जाते हैं।

उदाहरण के लिए, अन्य वेब घटकों के साथ संवाद करने के लिए सर्वलेट्स में थ्रेडलोक वैरिएबल का उपयोग करना, थ्रेड कंटेनर द्वारा बनाए जा रहे हैं और एक पूल में निष्क्रिय बनाए रखते हैं। थ्रेडलोक चर, यदि सही ढंग से साफ नहीं किए जाते हैं, तब तक वहां रहेंगे, संभवतः, वही वेब घटक अपने मूल्यों को ओवरराइट करता है।

बेशक, एक बार पहचानने के बाद, समस्या को आसानी से हल किया जा सकता है।


10

साक्षात्कारकर्ता एक परिपत्र संदर्भ समाधान की तलाश में हो सकता है:

    public static void main(String[] args) {
        while (true) {
            Element first = new Element();
            first.next = new Element();
            first.next.next = first;
        }
    }

यह संदर्भ गिनती कचरा संग्रहकर्ताओं के साथ एक क्लासिक समस्या है। फिर आप विनम्रता से समझाएंगे कि जेवीएम अधिक परिष्कृत एल्गोरिथ्म का उपयोग करता है जिसमें यह सीमा नहीं है।

-वे टार्ले


12
यह संदर्भ गिनती कचरा संग्रहकर्ताओं के साथ एक क्लासिक समस्या है। 15 साल पहले भी जावा ने रेफ काउंटिंग का इस्तेमाल नहीं किया था। संदर्भ। गिनती भी जीसी की तुलना में धीमी है।
बेस्ट जूल 5'11

4
स्मृति रिसाव नहीं। बस एक अनंत लूप।
एसेन स्कोव पेडर्सन

2
@ ऐसबेन प्रत्येक पुनरावृत्ति में, पिछले firstउपयोगी नहीं है और कचरा एकत्र किया जाना चाहिए। में संदर्भ गिनती कचरा लेनेवालों, वस्तु freeed नहीं किया जाएगा (अपने आप में) उस पर एक सक्रिय संदर्भ है क्योंकि वहाँ। अनंत लूप रिसाव को रोकने के लिए यहां है: जब आप प्रोग्राम चलाते हैं, तो मेमोरी अनिश्चित काल तक बढ़ेगी।
आरडीएस

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