मेरे पास बस एक साक्षात्कार था, और मुझे जावा के साथ एक मेमोरी लीक बनाने के लिए कहा गया था ।
कहने की जरूरत नहीं है, मुझे लगा कि कोई भी कैसे शुरू करने के लिए कोई सुराग नहीं होने पर बहुत गूंगा लगा।
एक उदाहरण क्या होगा?
मेरे पास बस एक साक्षात्कार था, और मुझे जावा के साथ एक मेमोरी लीक बनाने के लिए कहा गया था ।
कहने की जरूरत नहीं है, मुझे लगा कि कोई भी कैसे शुरू करने के लिए कोई सुराग नहीं होने पर बहुत गूंगा लगा।
एक उदाहरण क्या होगा?
जवाबों:
यहां शुद्ध जावा में एक सच्ची मेमोरी लीकेज (कोड से चलकर लेकिन अभी भी मेमोरी में संग्रहित होने वाली वस्तुएं) बनाने का एक अच्छा तरीका है:
ClassLoader
।new byte[1000000]
), एक स्थिर क्षेत्र में इसके लिए एक मजबूत संदर्भ संग्रहीत करता है, और फिर एक में खुद के लिए एक संदर्भ संग्रहीत करता है ThreadLocal
। अतिरिक्त मेमोरी आवंटित करना वैकल्पिक है (कक्षा के उदाहरण को लीक करना पर्याप्त है), लेकिन यह रिसाव का काम इतना तेज कर देगा।ClassLoader
इसे लोड किया गया था।ThreadLocal
ओरेकल के JDK में लागू होने के तरीके के कारण , यह मेमोरी लीक बनाता है:
Thread
में एक निजी क्षेत्र है threadLocals
, जो वास्तव में थ्रेड-स्थानीय मानों को संग्रहीत करता है।ThreadLocal
वस्तु के लिए एक कमजोर संदर्भ है , इसलिए उस ThreadLocal
वस्तु को कचरा एकत्र करने के बाद, उसका प्रवेश मानचित्र से हटा दिया जाता है।ThreadLocal
वस्तु को इंगित करता है जो इसकी कुंजी है , तो वह वस्तु न तो कचरा एकत्र करेगी और न ही मानचित्र से निकालेगी जब तक धागा रहता है।इस उदाहरण में, मजबूत संदर्भों की श्रृंखला इस तरह दिखती है:
Thread
ऑब्जेक्ट → threadLocals
नक्शा → उदाहरण वर्ग का उदाहरण → उदाहरण वर्ग → स्थिर ThreadLocal
फ़ील्ड → ThreadLocal
ऑब्जेक्ट।
(यह ClassLoader
वास्तव में लीक को बनाने में कोई भूमिका नहीं निभाता है, यह सिर्फ इस अतिरिक्त संदर्भ श्रृंखला के कारण रिसाव को बदतर बनाता है: उदाहरण वर्ग → ClassLoader
→ यह सभी वर्ग जो इसे लोड किया गया है। यह कई JVM कार्यान्वयनों में और भी बुरा था, विशेष रूप से पूर्व। जावा 7, क्योंकि कक्षाओं और ClassLoader
एस को सीधे परमिटेन में आवंटित किया गया था और कभी भी कचरा एकत्र नहीं किया गया था।
इस पैटर्न पर एक भिन्नता यह है कि क्यों अनुप्रयोग कंटेनर (जैसे टॉमकैट) एक छलनी की तरह मेमोरी को लीक कर सकते हैं यदि आप अक्सर एप्लिकेशन को फिर से तैयार करते हैं जो ThreadLocal
कि किसी तरह से एस का उपयोग करने के लिए होता है । यह कई सूक्ष्म कारणों से हो सकता है और अक्सर डिबग और / या ठीक करना मुश्किल होता है।
अपडेट : चूंकि बहुत से लोग इसके लिए पूछते रहते हैं, इसलिए यहां कुछ उदाहरण कोड हैं जो कार्रवाई में इस व्यवहार को दर्शाता है ।
स्थैतिक क्षेत्र धारण वस्तु संदर्भ [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 विकल्प , जैसे noclassgc
IBM JDK पर विकल्प जो अप्रयुक्त वर्ग कचरा संग्रह को रोकता है
IBM jdk सेटिंग देखें ।
close()
आमतौर पर अंतिम रूप में लागू नहीं होने वाली है थ्रेड चूंकि ब्लॉकिंग ऑपरेशन हो सकता है)। यह बंद नहीं करने के लिए एक बुरा अभ्यास है, लेकिन यह रिसाव का कारण नहीं बनता है। अनक्लोज्ड java.sql.Connection एक ही है।
intern
हैशटेबल सामग्री पर एक कमजोर संदर्भ है । इस प्रकार, यह है कचरा ठीक से एकत्र और एक रिसाव नहीं। (लेकिन IANAJP) mindprod.com/jgloss/interned.html#GC
एक साधारण बात यह है कि किसी गलत (या गैर-मौजूद) 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.
नीचे एक गैर-स्पष्ट मामला होगा जहां जावा लीक, भूल गए श्रोताओं के मानक मामले के अलावा, हैशैप्स में स्थैतिक संदर्भ, फर्जी / परिवर्तनीय कुंजी, या अपने जीवन-चक्र को समाप्त करने के लिए बस बिना किसी अवसर के अटक गए धागे।
File.deleteOnExit()
- हमेशा स्ट्रिंग लीक करता है, char[]
, इसलिए बाद में लागू नहीं होता है ; @ दानिएल को वोटों की कोई ज़रूरत नहीं है, हालाँकि।मैं थ्रेड्स पर ध्यान केंद्रित करूंगा, मानवरहित धागों के खतरे को दिखाने के लिए ज्यादातर, स्विंग को छूने की इच्छा नहीं रखते हैं।
Runtime.addShutdownHook
और नहीं हटाएं ... और फिर भी RemoveShutdownHook थ्रेडग्रुप क्लास में बग के कारण अनस्टार्ट किए गए थ्रेड्स के बारे में यह एकत्र नहीं हो सकता है, थ्रेडग्रुप को प्रभावी ढंग से लीक कर सकता है। JGroup का GossipRouter में रिसाव है।
बनाना, लेकिन शुरू नहीं करना, Thread
ऊपर के समान श्रेणी में जाता है।
एक धागा बनाना विरासत में मिला है ContextClassLoader
और AccessControlContext
, प्लस ThreadGroup
और कोई भी InheritedThreadLocal
, वे सभी संदर्भ संभावित लीक हैं, साथ ही क्लास लोडर और सभी स्थिर संदर्भों द्वारा लोड किए गए पूरे वर्गों और जेए-जेए के साथ। प्रभाव विशेष रूप से पूरे jucExecutor ढांचे के साथ दिखाई देता है जो एक सुपर सरल ThreadFactory
इंटरफ़ेस की सुविधा देता है , फिर भी अधिकांश डेवलपर्स के पास गुप्त खतरे का कोई सुराग नहीं है। इसके अलावा बहुत सारे पुस्तकालय अनुरोध पर धागे शुरू करते हैं (जिस तरह कई उद्योग लोकप्रिय पुस्तकालय हैं)।
ThreadLocal
कैश; वे कई मामलों में बुरे हैं। मुझे यकीन है कि सभी ने थ्रेडलोकल के आधार पर काफी सरल कैचेस देखे हैं, अच्छी खबर है: यदि थ्रेड जीवन के संदर्भ में क्लासोलेडर की अपेक्षा अधिक चल रहा है, तो यह एक शुद्ध अच्छा सा रिसाव है। जब तक वास्तव में ज़रूरत न हो, थ्रेडोकॉकल कैश का उपयोग न करें।
कॉलिंग ThreadGroup.destroy()
जब थ्रेडग्रुप के पास कोई थ्रेड नहीं है, लेकिन यह अभी भी चाइल्ड थ्रेडग्रुप रखता है। एक बुरा रिसाव जो थ्रेडग्रुप को उसके माता-पिता से दूर करने से रोकेगा, लेकिन सभी बच्चे संयुक्त राष्ट्र के अभिगम्य हो जाएंगे।
WeakHashMap और मूल्य (में) का उपयोग करना सीधे कुंजी को संदर्भित करता है। यह ढेर ढेर के बिना खोजने के लिए एक कठिन है। यह उन सभी विस्तारित पर लागू होता है Weak/SoftReference
जो एक कड़े संदर्भ को संरक्षित ऑब्जेक्ट पर वापस रख सकते हैं।
java.net.URL
HTTP (एस) प्रोटोकॉल के साथ प्रयोग करना और (!) से संसाधन लोड करना। यह विशेष है, KeepAliveCache
सिस्टम थ्रेडग्रुप में एक नया धागा बनाता है जो वर्तमान थ्रेड के संदर्भ क्लास लोडर को लीक करता है। थ्रेड पहले अनुरोध पर बनाया जाता है जब कोई जीवित धागा मौजूद नहीं होता है, इसलिए या तो आप भाग्यशाली हो सकते हैं या बस लीक हो सकते हैं। रिसाव पहले से ही जावा 7 में तय किया गया है और कोड जो ठीक से थ्रेड बनाता है, संदर्भ क्लास लोडर को हटा देता है। कुछ और मामले हैं (ImageFetcher की तरह, समान धागे बनाने के लिए भी ) तय किया गया ।
कंस्ट्रक्टर ( उदाहरण के लिए) में InflaterInputStream
पासिंग का उपयोग करना और फ़्लोटर का कॉल न करना । ठीक है, अगर आप कंस्ट्रक्टर में बस के साथ गुजरते हैं, तो कोई मौका नहीं ... और हां, स्ट्रीम पर कॉल करने से इन्फ्लोटर बंद नहीं होता है अगर यह मैन्युअली कंस्ट्रक्टर पैरामीटर के रूप में पास हो जाता है। यह एक सही लीक नहीं है क्योंकि यह अंतिम रूप से जारी किया जाएगा ... जब यह आवश्यक हो। उस क्षण तक यह देशी मेमोरी को इतनी बुरी तरह से खा लेता है कि यह Linux oom_killer को इस प्रक्रिया को मारने का कारण बन सकता है। मुख्य मुद्दा यह है कि जावा में अंतिम रूप देना बहुत अविश्वसनीय है और जी 1 ने इसे 7.0.2 तक बदतर बना दिया। कहानी का नैतिक: जितनी जल्दी हो सके देशी संसाधनों को जारी करें; फाइनल बहुत गरीब है।new java.util.zip.Inflater()
PNGImageDecoder
end()
new
close()
इसी मामले के साथ java.util.zip.Deflater
। यह एक बहुत खराब है क्योंकि डेफ्लिटर जावा में मेमोरी की भूख है, यानी हमेशा 15 बिट्स (अधिकतम) और 8 मेमोरी लेवल (9 अधिकतम) का उपयोग करता है, जो कई सैकड़ों केबी देशी मेमोरी आवंटित करता है। सौभाग्य से, Deflater
व्यापक रूप से उपयोग नहीं किया जाता है और मेरे ज्ञान में JDK का कोई दुरुपयोग नहीं है। हमेशा कॉल करें end()
यदि आप मैन्युअल रूप से एक Deflater
या बनाते हैं Inflater
। पिछले दो का सबसे अच्छा हिस्सा: आप उन्हें उपलब्ध सामान्य प्रोफाइलिंग टूल के माध्यम से नहीं पा सकते हैं।
(मेरे अनुरोध पर मेरे द्वारा सामना की गई कुछ और समय की आपदाओं को जोड़ सकते हैं।)
खुशकिस्मत रहें और सुरक्षित रहें; लीक बुराई है!
Creating but not starting a Thread...
Yikes, मैं कुछ सदियों पहले यह एक बुरी तरह से काट लिया गया था! (जावा 1.3)
unstarted
गिनती बढ़ाता है, बल्कि यह थ्रेड ग्रुप को नष्ट करने से रोकता है (कम बुराई लेकिन अभी भी एक रिसाव)
ThreadGroup.destroy()
जब थ्रेडग्रुप के पास कोई थ्रेड नहीं है ..." एक अविश्वसनीय रूप से सूक्ष्म बग है; मैं घंटों से इसका पीछा कर रहा था, रास्ता भटक गया क्योंकि मेरे नियंत्रण में धागे की गणना करते हुए जीयूआई ने कुछ नहीं दिखाया, लेकिन थ्रेड समूह और, संभवतः, कम से कम एक बच्चा समूह दूर नहीं जाएगा।
यहां अधिकांश उदाहरण "बहुत जटिल" हैं। वे एज केस हैं। इन उदाहरणों के साथ, प्रोग्रामर ने एक गलती की (जैसे बराबरी / हैशकोड को फिर से परिभाषित नहीं करना), या जेवीएम / जेएवीए (स्थिर के साथ वर्ग का भार) के एक कोने के मामले से काट दिया गया है। मुझे लगता है कि एक साक्षात्कारकर्ता चाहते हैं या यहां तक कि सबसे आम मामला उदाहरण का प्रकार नहीं है।
लेकिन स्मृति लीक के लिए वास्तव में सरल मामले हैं। कचरा संग्रहकर्ता केवल उस चीज को मुक्त करता है जिसे अब संदर्भित नहीं किया जाता है। हम जावा डेवलपर्स के रूप में स्मृति के बारे में परवाह नहीं है। हम जरूरत पड़ने पर इसे आवंटित करते हैं और इसे स्वचालित रूप से मुक्त कर देते हैं। ठीक।
लेकिन किसी भी लंबे समय तक रहने वाले आवेदन में साझा स्थिति होती है। यह कुछ भी हो सकता है, स्टेटिक्स, सिंग्लेट्स ... अक्सर गैर-तुच्छ अनुप्रयोग जटिल वस्तुओं को ग्राफ बनाने के लिए करते हैं। बस एक वस्तु को एक संग्रह से हटाने के लिए अशक्त या अधिक बार एक संदर्भ सेट करने के लिए भूल जाना स्मृति रिसाव बनाने के लिए पर्याप्त है।
बेशक सभी प्रकार के श्रोता (जैसे यूआई श्रोता), कैश, या कोई भी लंबे समय तक साझा किए गए राज्य स्मृति रिसाव का उत्पादन करते हैं यदि ठीक से संभाला नहीं जाता है। क्या समझा जाएगा कि यह जावा कोने का मामला नहीं है, या कूड़ा उठाने वाले के साथ समस्या नहीं है। यह एक डिज़ाइन समस्या है। हम डिजाइन करते हैं कि हम एक श्रोता को लंबे समय तक रहने वाली वस्तु में जोड़ते हैं, लेकिन जब जरूरत नहीं होती है तो हम श्रोता को नहीं हटाते हैं। हम वस्तुओं को कैश करते हैं, लेकिन हमारे पास उन्हें कैश से निकालने की कोई रणनीति नहीं है।
हमारे पास शायद एक जटिल ग्राफ है जो पिछले राज्य को संग्रहीत करता है जिसे गणना द्वारा आवश्यक है। लेकिन पिछला राज्य पहले और इसी तरह राज्य से जुड़ा हुआ है।
जैसे हमें SQL कनेक्शन या फाइल बंद करनी है। हमें संग्रह से तत्वों को शून्य और हटाने के लिए उचित संदर्भ सेट करने की आवश्यकता है। हमारे पास उचित कैशिंग रणनीति (अधिकतम स्मृति आकार, तत्वों की संख्या या टाइमर) होगी। सभी ऑब्जेक्ट जो एक श्रोता को सूचित करने की अनुमति देते हैं, उन्हें एक AddListener और RemoveListener दोनों विधि प्रदान करनी चाहिए। और जब इन नोटिफ़ायर का उपयोग नहीं किया जाता है, तो उन्हें अपनी श्रोता सूची साफ़ करनी चाहिए।
एक स्मृति रिसाव वास्तव में संभव है और पूरी तरह से अनुमानित है। विशेष भाषा सुविधाओं या कोने के मामलों के लिए कोई ज़रूरत नहीं है। मेमोरी लीक या तो एक संकेतक है कि कुछ गायब है या यहां तक कि डिजाइन की समस्याएं भी हैं।
WeakReference
) मौजूद हो एक से दूसरे में। यदि किसी ऑब्जेक्ट रेफरेंस में एक अतिरिक्त बिट था, तो यह "लक्ष्य के बारे में परवाह" संकेतक के लिए सहायक हो सकता है ...
PhantomReference
अगर किसी वस्तु के बारे में परवाह नहीं की गई है, तो सिस्टम को सूचनाएं प्रदान करता है (इसी तरह के माध्यम से )। WeakReference
कुछ हद तक करीब आता है, लेकिन इसे इस्तेमाल करने से पहले एक मजबूत संदर्भ में परिवर्तित किया जाना चाहिए; यदि एक जीसी चक्र होता है जबकि मजबूत संदर्भ मौजूद होता है, तो लक्ष्य को उपयोगी माना जाएगा।
उत्तर पूरी तरह से इस बात पर निर्भर करता है कि साक्षात्कारकर्ता ने क्या सोचा था कि वे पूछ रहे हैं।
क्या जावा रिसाव करना अभ्यास में संभव है? बेशक यह है, और अन्य उत्तरों में बहुत सारे उदाहरण हैं।
लेकिन कई मेटा-प्रश्न हैं जो पूछे जा रहे हैं?
मैं आपके मेटा-प्रश्न को "व्हाट्सएप जवाब दे सकता हूं जो मैं इस साक्षात्कार की स्थिति में उपयोग कर सकता हूं"। और इसलिए, मैं जावा के बजाय साक्षात्कार कौशल पर ध्यान केंद्रित करने जा रहा हूं। मेरा मानना है कि आप एक साक्षात्कार में एक प्रश्न के उत्तर को नहीं जानने की स्थिति को दोहराने की अधिक संभावना रखते हैं, क्योंकि आपको यह जानने की जरूरत है कि जावा रिसाव कैसे किया जाए। तो, उम्मीद है, यह मदद करेगा।
साक्षात्कार के लिए आपके द्वारा विकसित किए जा सकने वाले सबसे महत्वपूर्ण कौशलों में से एक है सक्रिय रूप से प्रश्नों को सुनना और उनका आशय निकालने के लिए साक्षात्कारकर्ता के साथ काम करना। यह न केवल आपको उनके प्रश्न का उत्तर देने का तरीका देता है, बल्कि वे यह भी दर्शाता है कि आपके पास कुछ महत्वपूर्ण संचार कौशल हैं। और जब यह कई समान रूप से प्रतिभाशाली डेवलपर्स के बीच एक विकल्प के लिए नीचे आता है, तो मैं हर बार जवाब देने से पहले सुनता, सोचता, और समझता हूं।
यदि आप 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
कक्षा से ऊपर के उदाहरण के समान )।Connection.close
अपने सभी एसक्यूएल कॉल के अंत में ब्लॉक में डाल दिया, तब तक मैं SQL डेटाबेस की कनेक्शन सीमा को बहुत हिट करता हूं। अतिरिक्त मज़े के लिए मैंने कुछ लंबे समय तक चलने वाली ओरेकल संग्रहीत प्रक्रियाओं को बुलाया जो डेटाबेस पर बहुत अधिक कॉलों को रोकने के लिए जावा पक्ष पर ताले की आवश्यकता थी।
संभवतः एक संभावित स्मृति रिसाव के सबसे सरल उदाहरणों में से एक है, और इससे कैसे बचा जाए, यह 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
)? यह संदर्भ किसी विशाल वस्तु को जीवित रख सकता है ...
जब भी आप उन वस्तुओं का संदर्भ देते हैं, जिनकी आपको स्मृति रिसाव की आवश्यकता नहीं होती है। जावा में मेमोरी लीक खुद को कैसे प्रकट करते हैं और इसके बारे में आप क्या कर सकते हैं, इसके उदाहरणों के लिए जावा कार्यक्रमों में हैंडलिंग मेमोरी लीक देखें ।
...then the question of "how do you create a memory leak in X?" becomes meaningless, since it's possible in any language.
मैं यह नहीं देखता कि आप उस निष्कर्ष को कैसे खींच रहे हैं। जावा में किसी भी परिभाषा से मेमोरी लीक बनाने के कम तरीके हैं । यह निश्चित रूप से अभी भी एक वैध प्रश्न है।
आप 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();
}
}
}
मैं अपने उत्तर को यहाँ से कॉपी कर सकता हूँ: जावा में मेमोरी रिसाव का सबसे आसान तरीका?
"कंप्यूटर विज्ञान (या इस संदर्भ में रिसाव) में एक मेमोरी लीक, तब होता है जब एक कंप्यूटर प्रोग्राम मेमोरी की खपत करता है, लेकिन इसे ऑपरेटिंग सिस्टम पर वापस जारी करने में असमर्थ होता है।" (विकिपीडिया)
आसान जवाब है: आप नहीं कर सकते। जावा स्वचालित स्मृति प्रबंधन करता है और आपके लिए आवश्यक संसाधनों को मुक्त करेगा। आप ऐसा होने से नहीं रोक सकते। यह हमेशा संसाधनों को जारी करने में सक्षम होगा। मैनुअल मेमोरी प्रबंधन वाले कार्यक्रमों में, यह अलग है। आप मॉलॉक () का उपयोग करके सी में कुछ मेमोरी प्राप्त कर सकते हैं। मेमोरी को मुक्त करने के लिए, आपको उस पॉइंटर की आवश्यकता होती है जिसे मॉलोक ने लौटाया और उस पर मुफ्त () कॉल करें। लेकिन अगर आपके पास अब पॉइंटर नहीं है (अधिलेखित, या आजीवन अधिक हो गया है), तो आप दुर्भाग्य से इस मेमोरी को मुक्त करने में असमर्थ हैं और इस प्रकार आपके पास मेमोरी लीक है।
अब तक के सभी अन्य जवाब मेरी परिभाषा में हैं कि वास्तव में मेमोरी लीक नहीं है। वे सभी व्यर्थ सामान को असली तेजी से मेमोरी को भरने का लक्ष्य रखते हैं। लेकिन किसी भी समय आप अभी भी आपके द्वारा बनाई गई वस्तुओं को निष्क्रिय कर सकते हैं और इस तरह मेमोरी को मुक्त कर सकते हैं -> NO LEAK। acconrad का जवाब बहुत करीब आता है, क्योंकि मुझे स्वीकार करना होगा क्योंकि उसका समाधान प्रभावी ढंग से कचरा कलेक्टर को "एक अंतहीन लूप में मजबूर करके" क्रैश करना है)।
लंबा उत्तर है: आप जेएनआई का उपयोग करके जावा के लिए एक पुस्तकालय लिखकर मेमोरी लीक प्राप्त कर सकते हैं, जिसमें मैनुअल मेमोरी प्रबंधन हो सकता है और इस प्रकार मेमोरी लीक हो सकती है। यदि आप इस लाइब्रेरी को कहते हैं, तो आपकी जावा प्रक्रिया मेमोरी को लीक कर देगी। या, आप JVM में बग हो सकते हैं, ताकि JVM मेमोरी खो दे। जेवीएम में संभवतः कीड़े हैं, यहां तक कि कुछ ज्ञात भी हो सकते हैं क्योंकि कचरा संग्रह उस तुच्छ नहीं है, लेकिन फिर भी यह एक बग है। डिजाइन से यह संभव नहीं है। आप इस तरह के बग से प्रभावित होने वाले कुछ जावा कोड के लिए पूछ सकते हैं। क्षमा करें, मैं एक नहीं जानता और यह अगले जावा संस्करण में वैसे भी अब बग नहीं हो सकता है।
यहाँ 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();
...
ऐसा करने से स्ट्रींगलेकर के उदाहरण को छोड़ दिए जाने के बाद भी मूल लंबे स्ट्रिंग और मेमोरी में व्युत्पन्न दोनों को रखा जाएगा।
muchSmallerString
जाता है (क्योंकि StringLeaker
वस्तु नष्ट हो जाती है), लंबी स्ट्रिंग को भी मुक्त किया जाएगा। जिसे मैं मेमोरी लीक कहता हूं वह मेमोरी है जिसे जेवीएम के इस उदाहरण में कभी भी मुक्त नहीं किया जा सकता है। हालाँकि, आपने स्वयं को दिखाया है कि मेमोरी को कैसे खाली किया जाए this.muchSmallerString=new String(this.muchSmallerString)
:। एक वास्तविक मेमोरी लीक के साथ, कुछ भी नहीं है जो आप कर सकते हैं।
intern
मामला "स्मृति रिसाव" की तुलना में "मेमोरी आश्चर्य" का अधिक हो सकता है। .intern()
हालांकि, सब्रिंग आईएनजी, निश्चित रूप से एक ऐसी स्थिति बनाता है जहां लंबी स्ट्रिंग का संदर्भ संरक्षित है और इसे मुक्त नहीं किया जा सकता है।
GUI कोड में इसका एक सामान्य उदाहरण एक विजेट / कंपोनेंट बनाते समय और कुछ स्टैटिक / एप्लिकेशन स्कोप्ड ऑब्जेक्ट में एक श्रोता को जोड़ने और फिर विजेट के नष्ट होने पर श्रोता को नहीं हटाने का है। न केवल आपको एक स्मृति रिसाव मिलता है, बल्कि एक प्रदर्शन भी हिट होता है जब आप जो भी आग की घटनाओं को सुन रहे होते हैं, आपके सभी पुराने श्रोताओं को भी बुलाया जाता है।
किसी भी सर्वलेट कंटेनर (टॉमकैट, जेट्टी, ग्लासफिश, जो भी ...) में चल रहे किसी भी वेब एप्लिकेशन को लें। एप्लिकेशन को पंक्ति में 10 या 20 बार रिडिप्लाइ करें (यह सर्वर के ऑटोडेप्लॉय डायरेक्टरी में केवल WAR को छूने के लिए पर्याप्त हो सकता है।
जब तक किसी ने वास्तव में इसका परीक्षण नहीं किया है, तब तक संभावना अधिक होती है कि आपको कुछ पुनर्वितरण के बाद एक आउटऑफमेरी एरर मिल जाएगा, क्योंकि आवेदन ने खुद के बाद सफाई करने का ध्यान नहीं रखा। तुम भी इस परीक्षण के साथ अपने सर्वर में एक बग मिल सकता है।
समस्या यह है, कंटेनर का जीवनकाल आपके आवेदन के जीवनकाल से अधिक लंबा है। आपको यह सुनिश्चित करना होगा कि कंटेनर के सभी संदर्भ वस्तुओं या आपके आवेदन की कक्षाओं में एकत्रित हो सकते हैं।
यदि आपके वेब ऐप के अनपेक्षित रूप से जीवित रहने का केवल एक संदर्भ है, तो संबंधित क्लास लोडर और इसके परिणामस्वरूप आपके वेब ऐप के सभी वर्ग कचरा एकत्र नहीं कर सकते हैं।
आपके एप्लिकेशन द्वारा शुरू किए गए थ्रेड्स, थ्रेडलोकल वैरिएबल, लॉगिंग एपेंडर्स क्लासॉकर लीक का कारण बनने वाले कुछ सामान्य संदिग्ध हैं।
शायद जेएनआई के माध्यम से बाहरी देशी कोड का उपयोग करके?
शुद्ध जावा के साथ, यह लगभग असंभव है।
लेकिन यह एक "मानक" प्रकार के मेमोरी लीक के बारे में है, जब आप मेमोरी को अब एक्सेस नहीं कर सकते हैं, लेकिन यह अभी भी एप्लिकेशन के स्वामित्व में है। आप इसके बजाय अप्रयुक्त वस्तुओं के संदर्भ रख सकते हैं, या बाद में उन्हें बंद किए बिना धाराओं को खोल सकते हैं।
एक बार पर्मगेन और एक्सएमएल पार्सिंग के संबंध में मेरे पास एक अच्छा "मेमोरी लीक" है। XML पार्सर जिसका हमने उपयोग किया (मुझे याद नहीं है कि यह कौन सा था) टैग नामों पर एक String.intern () किया, जिससे तुलना तेजी से हो सके। हमारे ग्राहकों में से एक को XML विशेषताओं या पाठ में डेटा मानों को संग्रहीत करने का बहुत अच्छा विचार था, लेकिन tagnames के रूप में, इसलिए हमारे पास एक दस्तावेज़ था:
<data>
<1>bla</1>
<2>foo</>
...
</data>
वास्तव में, वे संख्याओं का उपयोग नहीं करते थे, लेकिन लंबे समय तक पाठ्य आईडी (लगभग 20 अक्षर), जो अद्वितीय थे और प्रतिदिन 10-15 मिलियन की दर से आते थे। यह एक दिन में 200 एमबी बकवास बनाता है, जिसे फिर कभी ज़रूरत नहीं होती है, और कभी भी जीसीड (क्योंकि यह पर्मगेन में नहीं है)। हमारे पास 512 एमबी की अनुमति थी, इसलिए आउट-ऑफ-मेमोरी अपवाद (OOME) आने में दो दिन लग गए ...
स्मृति रिसाव क्या है:
विशिष्ट उदाहरण:
वस्तुओं का एक कैश चीजों को गड़बड़ाने का एक अच्छा प्रारंभिक बिंदु है।
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 (केवल हाल ही में उपयोग की गई वस्तुओं को कैश में रखता है) का उपयोग करता है।
ज़रूर, आप चीजों को और अधिक जटिल बना सकते हैं:
अक्सर क्या होता है:
यदि इस Info ऑब्जेक्ट में अन्य ऑब्जेक्ट्स के संदर्भ हैं, जिसमें फिर से अन्य ऑब्जेक्ट्स के संदर्भ हैं। एक तरह से आप इसे किसी प्रकार की मेमोरी लीक (खराब डिज़ाइन के कारण) भी मान सकते हैं।
मुझे लगा कि यह दिलचस्प है कि किसी ने आंतरिक वर्ग के उदाहरणों का इस्तेमाल नहीं किया। यदि आपके पास एक आंतरिक वर्ग है; यह स्वाभाविक रूप से युक्त वर्ग के लिए एक संदर्भ रखता है। बेशक, यह तकनीकी रूप से एक स्मृति रिसाव नहीं है क्योंकि जावा विल अंततः इसे साफ कर देगा; लेकिन इससे कक्षाएं प्रत्याशित रूप से अधिक समय तक घूम सकती हैं।
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.
}
}
मैंने एक अफवाह भी सुनी है कि यदि आपके पास एक चर है जो एक विशिष्ट राशि से अधिक समय तक मौजूद है; जावा मानता है कि यह हमेशा मौजूद रहेगा और वास्तव में इसे साफ करने की कभी कोशिश नहीं करेगा अगर अब कोड में नहीं पहुंचा जा सकता है। लेकिन यह पूरी तरह से असत्यापित है।
मुझे हाल ही में 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 द्वारा विशालवाद के संदर्भ को जीवित रखा गया, जिससे स्मृति रिसाव हो गया।
साक्षात्कारकर्ता शायद नीचे दिए गए कोड की तरह एक परिपत्र संदर्भ की तलाश कर रहा था (जो संयोगवश बहुत पुरानी जेवीएम में मेमोरी को लीक करता था जो संदर्भ गिनती का उपयोग करता था, जो कि कोई और मामला नहीं है)। लेकिन यह एक बहुत ही अस्पष्ट सवाल है, इसलिए यह 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. */
}
}
फिर आप यह समझा सकते हैं कि यह तकनीकी रूप से एक मेमोरी लीक है, लेकिन वास्तव में लीक जेवीएम में मूल कोड के कारण होता है जो अंतर्निहित मूल संसाधनों को आवंटित करता है, जिन्हें आपके जावा कोड द्वारा मुक्त नहीं किया गया था।
दिन के अंत में, एक आधुनिक जेवीएम के साथ, आपको कुछ जावा कोड लिखने की आवश्यकता होती है जो जेवीएम की जागरूकता के सामान्य दायरे के बाहर एक देशी संसाधन आवंटित करता है।
एक स्थैतिक मानचित्र बनाएं और इसमें कठिन संदर्भ जोड़ते रहें। वे कभी 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); }
}
आप उस कक्षा की अंतिम विधि में एक वर्ग का नया उदाहरण बनाकर एक चलती स्मृति रिसाव बना सकते हैं। बोनस अंक अगर फाइनलाइज़र कई उदाहरण बनाता है। यहाँ एक सरल कार्यक्रम है जो कुछ ही सेकंडों में और आपके ढेर के आकार के आधार पर कुछ मिनटों के बीच पूरे ढेर को लीक करता है:
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());
}
}
}
मुझे नहीं लगता कि किसी ने भी अभी तक यह कहा है: आप अंतिम रूप () विधि को ओवरराइड करके किसी वस्तु को फिर से जीवित कर सकते हैं जैसे कि अंतिम रूप () कहीं न कहीं इस संदर्भ को संग्रहीत करता है। कचरा संग्रहकर्ता को केवल एक बार वस्तु पर बुलाया जाएगा ताकि उसके बाद वस्तु कभी नष्ट न हो।
finalize()
नहीं बुलाया जाएगा, लेकिन एक बार और अधिक संदर्भ नहीं होगा, तो वस्तु को एकत्र किया जाएगा। कचरा बीनने वाले को 'कहा नहीं' जाता है।
finalize()
विधि को केवल एक बार जेवीएम द्वारा बुलाया जा सकता है, लेकिन इसका मतलब यह नहीं है कि वस्तु को फिर से जीवित करने और फिर से डीएरफेर किए जाने पर इसे फिर से इकट्ठा नहीं किया जा सकता है। यदि finalize()
विधि में संसाधन समापन कोड है तो यह कोड फिर से नहीं चलेगा, इससे मेमोरी रिसाव हो सकता है।
मैं हाल ही में एक अधिक सूक्ष्म प्रकार के रिसोर्स लीक पर आया था। हम क्लास लोडर के 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 एमबी से अधिक नहीं होगी, जो पुनरावृत्ति गणना से स्वतंत्र है।
काफी सरल और आश्चर्यजनक।
जैसा कि बहुत से लोगों ने सुझाव दिया है, रिसोर्स लीक्स काफी आसान कारण हैं - जैसे कि जेडीबीसी उदाहरण। वास्तविक मेमोरी लीक थोड़ा कठिन है - खासकर यदि आप JVM के टूटे हुए बिट्स पर भरोसा नहीं कर रहे हैं तो यह आपके लिए ...
उन वस्तुओं को बनाने के विचार जिनके पास बहुत बड़ा पदचिह्न है और फिर उन्हें एक्सेस नहीं कर पा रहे हैं वे वास्तविक मेमोरी लीक भी नहीं हैं। यदि कुछ भी इसे एक्सेस नहीं कर सकता है, तो यह कचरा एकत्र किया जाएगा, और अगर कुछ इसे एक्सेस कर सकता है तो यह रिसाव नहीं है ...
एक तरीका यह है कि इस्तेमाल किया काम हालांकि करने के लिए - और अगर यह अभी भी होता है मैं नहीं जानता कि - एक तीन गहरी परिपत्र श्रृंखला है। जैसे ऑब्जेक्ट ए में ऑब्जेक्ट बी का संदर्भ है, ऑब्जेक्ट बी में ऑब्जेक्ट सी का संदर्भ है और ऑब्जेक्ट सी में ऑब्जेक्ट ए का संदर्भ है। जीसी यह जानने के लिए पर्याप्त चतुर था कि दो गहरी श्रृंखला - जैसे कि ए <-> बी में है। - ए और बी को किसी और चीज द्वारा सुलभ नहीं होने पर सुरक्षित रूप से एकत्र किया जा सकता है, लेकिन तीन-तरफ़ा श्रृंखला को संभाल नहीं सकता है ...
संभावित रूप से विशाल मेमोरी लीक बनाने का एक और तरीका है 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
।
समाधान? बस सीधे कुंजी / मूल्य (जैसा कि आप शायद पहले से ही करते हैं) को बचाने के बजाय बचाएंMap.Entry
।
समाप्त होने तक धागे एकत्र नहीं किए जाते हैं। वे कचरा संग्रहण की जड़ों के रूप में काम करते हैं । वे उन कुछ वस्तुओं में से एक हैं जिन्हें केवल उनके बारे में भूलकर या उनके संदर्भों को साफ़ करके पुनः प्राप्त नहीं किया जाएगा।
विचार करें: एक श्रमिक धागे को समाप्त करने का मूल पैटर्न थ्रेड द्वारा देखे गए कुछ स्थिति चर को सेट करना है। थ्रेड समय-समय पर चर की जांच कर सकता है और इसे समाप्त करने के लिए एक संकेत के रूप में उपयोग कर सकता है। यदि चर घोषित नहीं किया गया है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
मर जाते हैं कभी नहीं होगा।
(* संपादित *)
मुझे लगता है कि एक वैध उदाहरण थ्रेडलोकल वैरिएबल का उपयोग ऐसे वातावरण में किया जा सकता है जहां थ्रेड पूल किए जाते हैं।
उदाहरण के लिए, अन्य वेब घटकों के साथ संवाद करने के लिए सर्वलेट्स में थ्रेडलोक वैरिएबल का उपयोग करना, थ्रेड कंटेनर द्वारा बनाए जा रहे हैं और एक पूल में निष्क्रिय बनाए रखते हैं। थ्रेडलोक चर, यदि सही ढंग से साफ नहीं किए जाते हैं, तब तक वहां रहेंगे, संभवतः, वही वेब घटक अपने मूल्यों को ओवरराइट करता है।
बेशक, एक बार पहचानने के बाद, समस्या को आसानी से हल किया जा सकता है।
साक्षात्कारकर्ता एक परिपत्र संदर्भ समाधान की तलाश में हो सकता है:
public static void main(String[] args) {
while (true) {
Element first = new Element();
first.next = new Element();
first.next.next = first;
}
}
यह संदर्भ गिनती कचरा संग्रहकर्ताओं के साथ एक क्लासिक समस्या है। फिर आप विनम्रता से समझाएंगे कि जेवीएम अधिक परिष्कृत एल्गोरिथ्म का उपयोग करता है जिसमें यह सीमा नहीं है।
-वे टार्ले
first
उपयोगी नहीं है और कचरा एकत्र किया जाना चाहिए। में संदर्भ गिनती कचरा लेनेवालों, वस्तु freeed नहीं किया जाएगा (अपने आप में) उस पर एक सक्रिय संदर्भ है क्योंकि वहाँ। अनंत लूप रिसाव को रोकने के लिए यहां है: जब आप प्रोग्राम चलाते हैं, तो मेमोरी अनिश्चित काल तक बढ़ेगी।