2009-03-09 16 views
36

मैं पिछले डेवलपर से कुछ एप्लिकेशन ले रहा हूं। जब मैं ग्रहण के माध्यम से अनुप्रयोग चलाता हूं, तो मुझे स्मृति उपयोग दिखाई देता है और ढेर का आकार बहुत बढ़ जाता है। आगे की जांच के बाद, मैं देखता हूं कि वे एक लूप के साथ-साथ अन्य चीजों में एक वस्तु बना रहे थे।कुछ जावा मेमोरी प्रबंधन सर्वोत्तम प्रथाओं क्या हैं?

मैंने आगे बढ़ना शुरू कर दिया और कुछ साफ कर दिया। लेकिन जितना अधिक मैं गया, उतना अधिक प्रश्न मुझे पसंद आया "क्या यह वास्तव में कुछ भी करेगा?"

उदाहरण के लिए, ऊपर वर्णित लूप के बाहर एक चर घोषित करने और लूप में अपना मान सेट करने के बजाय ... उन्होंने ऑब्जेक्ट को लूप में बनाया है। क्या मेरा मतलब है:

for(int i=0; i < arrayOfStuff.size(); i++) { 
    String something = (String) arrayOfStuff.get(i); 
    ... 
} 

बनाम

String something = null; 
for(int i=0; i < arrayOfStuff.size(); i++) { 
    something = (String) arrayOfStuff.get(i); 
} 

Am मैं गलत कहना है कि नीचे पाश बेहतर है? शायद मैं गलत हूँ।

इसके अलावा, ऊपर दूसरे लूप के बाद क्या, मैंने "कुछ" वापस शून्य पर सेट किया? क्या यह कुछ स्मृति को साफ़ करेगा?

किसी भी मामले में, कुछ अच्छे मेमोरी प्रबंधन सर्वोत्तम अभ्यास क्या हैं जो मैं कर सकता हूं जो मेरे अनुप्रयोगों में मेरे मेमोरी उपयोग को कम रखने में मदद करेगा?

अद्यतन:

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

+0

अपना संपादन पुनः पढ़ें: जैसा कि तीन लोगों ने अभी तक कहा है, प्रोफाइल करें! –

+0

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

+2

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

उत्तर

36

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

पुस्तकें जो इन चीजों को विस्तार से कवर करती हैं (उपयोगकर्ता के परिप्रेक्ष्य से) Effective Java 2 और Implementation Patterns हैं।

यदि आप प्रदर्शन और वीएम के शिष्टाचार के बारे में अधिक जानकारी प्राप्त करते हैं तो आपको Brian Goetz से वार्ताएं पढ़ने या पढ़ने की आवश्यकता है।

+0

" न्यूनतम दायरे का उपयोग संभव है "क्या इसे सामान्य रूप से सबसे अच्छा अभ्यास माना जाता है? – KillBill

+0

मुझे लगता है कि आम तौर पर हाँ। लेकिन निश्चित रूप से उस सामान्यीकरण के अपवाद होना चाहिए। – cherouvim

9

उन दो loops something के दायरे को छोड़कर बराबर हैं; विवरण के लिए this question देखें।

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

+3

पूरी तरह से "उपाय, अनुमान नहीं है" के लिए +1। –

5

दो लूप मूल रूप से समान मात्रा में स्मृति का उपयोग करेंगे, कोई अंतर नगण्य होगा। "कुछ स्ट्रिंग" केवल एक ऑब्जेक्ट का संदर्भ बनाता है, न कि स्वयं में एक नई वस्तु है और इस प्रकार उपयोग की जाने वाली कोई अतिरिक्त मेमोरी छोटी है। इसके अलावा, जेएमवी के साथ कंपाइलर/संयुक्त रूप से जेनरेट किए गए कोड को वैसे भी अनुकूलित करेगा।

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

आप कमजोर संदर्भ, और अन्य विशेष स्मृति प्रबंधन कक्षाओं को भी देख सकते हैं।

अन्त में, यह ध्यान रखें कि यदि एक आवेदन स्मृति तक ले जाता है, वहाँ इसके लिए एक कारण हो सकता है ....

अद्यतन स्मृति प्रबंधन के लिए महत्वपूर्ण डेटा संरचनाओं, साथ ही कितना है प्रदर्शन की आपको आवश्यकता है/कब। ट्रेडऑफ अक्सर स्मृति और सीपीयू चक्र के बीच होता है।

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

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

4

पहला लूप बेहतर है। क्योंकि

  • चर कुछ हो जाएगा स्पष्ट तेजी से (सैद्धांतिक)
  • कार्यक्रम को पढ़ने के लिए बेहतर है।

लेकिन स्मृति के बिंदु से यह अप्रासंगिक है।

यदि आपके पास स्मृति समस्याएं हैं तो आपको प्रोफ़ाइल का उपभोग करना चाहिए।

+0

क्षमा करें, अगर मैं गलत हूं तो कृपया मुझे सही करें, लेकिन पहला पाश प्रत्येक लूप पर एक नए चर के लिए स्मृति आवंटित करता है। यदि लूप 100 गुना चलता है, तो यह केवल एक स्ट्रिंग को ओवरराइड करने और केवल 1 स्ट्रिंग (जैसे दूसरे लूप में) के लिए मेमोरी आवंटित करने के बजाय 100 तारों को बेहतर बनाने के लिए बेहतर होता है? – Mapisto

+0

किसी विधि के सभी स्थानीय चर के लिए स्टैक पर स्मृति विधि प्रविष्टि पर आवंटित की जाती है, न कि कोड में घोषित बिंदु पर। इस तरह के चर का आकार 4 बाइट्स हैं। (64 बिट वीएम पर संभव 8 बाइट्स)। स्ट्रिंग के लिए आकार ढेर पर आवंटित किया जाता है, न कि ढेर पर। यह आवंटन "नया" कीवर्ड पर होता है। तारों के आवंटन में कोई अंतर नहीं है। – Horcrux7

8

आपके दोनों कोड नमूने में कोई वस्तु नहीं बनाई गई है। आप केवल एक स्ट्रिंग के ऑब्जेक्ट संदर्भ को सेट करते हैं जो पहले से ही arrayOfStuff में है। तो स्मृति की कोई फर्क नहीं पड़ता।

1

ठीक है, पहला लूप वास्तव में बेहतर है, क्योंकि कुछ का दायरा छोटा है। स्मृति प्रबंधन के संबंध में - यह एक बड़ा अंतर नहीं बनाता है।

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

1

पहला उदाहरण ठीक है। लूप (बहुत सस्ता और त्वरित) के माध्यम से हर बार एक स्टैक वैरिएबल आवंटन और डीलोकेशन के अलावा, वहां कोई स्मृति आवंटन नहीं होता है।

कारण यह है कि 'आवंटित' होने वाला सभी एक संदर्भ है, जो एक 4 बाइट स्टैक वैरिएबल है (वैसे भी 32 बिट सिस्टम पर)। स्टैक वैरिएबल को स्टैक के शीर्ष का प्रतिनिधित्व करने वाले मेमोरी एड्रेस में जोड़कर 'आवंटित' किया जाता है, और यह बहुत तेज़ और सस्ता है।

for (int i = 0; i < some_large_num; i++) 
{ 
    String something = new String(); 
    //do stuff with something 
} 

कि के रूप में वास्तव में स्मृति allocatiton कर रही है:

क्या आप में से सावधान रहने की जरूरत है जैसे छोरों के लिए है।

+0

प्रत्येक पुनरावृत्ति के लिए ढेर पर भी आवंटन नहीं है। ये स्थानीय वैरिएबल टेबल (एक बार) विधि में आवंटित किए जाते हैं, फिर भी लूप समाप्त होने पर वेरिएबल स्वचालित रूप से आउट-ऑफ-स्कोप चला जाता है। –

+0

हाँ, लेकिन एक नई स्ट्रिंग ऑब्जेक्ट प्रत्येक पुनरावृत्ति को बनाया गया है जो एक स्टैक आवंटन से अधिक महंगा है। – workmad3

5

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

अंत में, # 1 चीज़ जो आपको करना चाहिए से बचें: कभी भी फ़ाइनलाइज़र का उपयोग न करें।फाइनलाइजर्स कचरा संग्रह में हस्तक्षेप करते हैं, क्योंकि वस्तु को केवल मुक्त नहीं किया जा सकता है, लेकिन इसे अंतिम रूप देने के लिए कतारबद्ध किया जाना चाहिए, जो हो सकता है या नहीं भी हो सकता है। फाइनलाइजर्स का कभी भी उपयोग करना सबसे अच्छा नहीं है।

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

+0

ऑब्जेक्ट उन्मुख, कचरा एकत्रित भाषाओं में, खराब प्रदर्शन अक्सर बड़ी संख्या में दीर्घकालिक वस्तुओं के कारण होता है, खासकर अगर संरचना की कई परतें होती हैं। उदाहरण के लिए, यदि मैं सेल ऑब्जेक्ट्स की 2 डी सरणी के साथ एक स्प्रेडशीट ऑब्जेक्ट बनाता हूं, जहां प्रत्येक ऑब्जेक्ट में रंग ऑब्जेक्ट, वैल्यू ऑब्जेक्ट और स्वरूपण ऑब्जेक्ट होता है, और प्रत्येक स्वरूपण ऑब्जेक्ट में ..... अच्छा है, आपको विचार मिलता है। यह ऑब्जेक्ट ग्राफ़ से निपटने के लिए जीसी को काफी समय लगेगा। इसके अलावा मेमोरी इंडिकेशन की परतें अंतराल का कारण बनती हैं (यह स्मॉलटाक में है, वैसे भी। मैं जावा के बारे में निश्चित नहीं हूं)। – ahoffer

1

यदि आप पहले से नहीं हैं, तो मैं Eclipse Test & Performance Tools Platform (टीपीटीपी) स्थापित करने का सुझाव देता हूं। यदि आप ढेर को डंप और निरीक्षण करना चाहते हैं, तो एसडीके jmap and jhat उपकरण देखें। Monitoring and Managing Java SE 6 Platform Applications भी देखें।

+1

चूंकि टीपीटीपी अब (5 साल बाद) अच्छी तरह से और वास्तव में मर चुका है, jvisualvm के लिए जाएं जो जेडीके के साथ जहाजों - और जीसी लॉग का विश्लेषण (http://www.tagtraum.com/gcviewer.html या https://github.com/chewiebug/GCViewer)। –

4

मेरी राय में, आपको इन तरह सूक्ष्म अनुकूलन से बचना चाहिए। उन्हें बहुत सारे मस्तिष्क चक्र खर्च होते हैं, लेकिन अधिकांश समय का बहुत कम प्रभाव पड़ता है।

आपके एप्लिकेशन में शायद कुछ केंद्रीय डेटा संरचनाएं हैं। वे हैं जिनके बारे में आपको चिंतित होना चाहिए। उदाहरण के लिए, यदि आप अंतर्निहित संरचना के बार-बार आकार बदलने से बचने के लिए उन्हें आकार के अच्छे अनुमान के साथ प्रीलाकेट करते हैं। यह विशेष रूप से StringBuffer, ArrayList, HashMap और इसी तरह लागू होता है। उन संरचनाओं को अच्छी तरह से एक्सेस करें, इसलिए आपको बहुत प्रतिलिपि बनाने की आवश्यकता नहीं है।

डेटा संरचनाओं तक पहुंचने के लिए उचित एल्गोरिदम का उपयोग करें। निम्नतम स्तर पर, आपके द्वारा वर्णित लूप की तरह, Iterator s का उपयोग करें, या कम से कम .size() पर कॉल करने से बचें। (हां, आप हर बार इसके आकार के लिए सूची पूछ रहे हैं, जो ज्यादातर समय नहीं बदलता है।) बीटीडब्ल्यू, मैंने अक्सर Map एस के साथ एक समान गलती देखी है। लोग keySet() और get पर पहले स्थान पर केवल entrySet() पर फिर से शुरू करने के बजाय प्रत्येक मान को फिर से भरते हैं। अतिरिक्त CPU चक्रों के लिए मेमोरी मैनेजर आपको धन्यवाद देगा।

2

ऊपर दिए गए एक पोस्टर के रूप में, अनुमान लगाने की कोशिश करने के बजाए आपके प्रोग्राम के कुछ हिस्सों के स्मृति (और/या cpu) उपयोग को मापने के लिए प्रोफाइलर का उपयोग करें। जो आप पाएंगे उस पर आप आश्चर्यचकित भी हो सकते हैं!

इसके साथ एक अतिरिक्त लाभ भी है। आप अपनी प्रोग्रामिंग भाषा और आपके आवेदन के बारे में और अधिक समझेंगे।

मैं प्रोफाइलिंग के लिए VisualVM का उपयोग करता हूं और इसकी बहुत अनुशंसा करता हूं। यह जेडीके/जेआर वितरण के साथ आता है।

0

"क्या मुझे यह कहना गलत है कि नीचे लूप बेहतर है?", जवाब नहीं है, न केवल बेहतर है, उसी मामले में आवश्यक है ... परिवर्तनीय परिभाषा (सामग्री नहीं), स्मृति में बनाई गई है ढेर, और सीमित है, पहले उदाहरण में, प्रत्येक पाश इस स्मृति में एक उदाहरण बनाते हैं, और यदि "arrayOfStuff" का आकार बड़ा है, तो "स्मृति त्रुटि से बाहर: जावा हीप स्पेस" हो सकता है ....

0

जो मैं समझता हूं उससे आप देख रहे हैं, नीचे लूप बेहतर नहीं है। यदि आप एक संदर्भ (पूर्व-कुछ) का पुन: उपयोग करने का प्रयास कर रहे हैं, तो भी तथ्य यह है कि ऑब्जेक्ट (Ex - arrayOfStuff.get (i)) अभी भी सूची (arrayOfStuff) से संदर्भित किया जा रहा है। वस्तुओं को संग्रह के लिए योग्य बनाने के लिए, उन्हें किसी भी स्थान से संदर्भित नहीं किया जाना चाहिए। यदि आप इस बिंदु के बाद सूची के जीवन के बारे में निश्चित हैं, तो आप एक अलग लूप के भीतर से वस्तुओं को निकालने/मुक्त करने का निर्णय ले सकते हैं।

एक स्थिर परिप्रेक्ष्य से ऑप्टिमाइज़ेशन किया जा सकता है (यानी किसी भी अन्य थ्रेड से इस सूची में कोई संशोधन नहीं हो रहा है), आकार() आमंत्रण बार-बार से बचने के लिए बेहतर है। ऐसा है कि यदि आप आकार बदलने की उम्मीद नहीं कर रहे हैं, तो फिर बार-बार इसकी गणना क्यों करें; आखिरकार यह एक सरणी नहीं है। लम्बाई, यह list.size() है।

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