Java Language
जावा मेमोरी मैनेजमेंट
खोज…
टिप्पणियों
जावा में, ऑब्जेक्ट्स को ढेर में आवंटित किया जाता है, और हीप मेमोरी स्वचालित कचरा संग्रह द्वारा पुनः प्राप्त की जाती है। एक एप्लिकेशन प्रोग्राम स्पष्ट रूप से जावा ऑब्जेक्ट को हटा नहीं सकता है।
जावा कचरा संग्रह के मूल सिद्धांत कचरा संग्रह उदाहरण में वर्णित हैं। अन्य उदाहरणों में अंतिम रूप से वर्णन किया गया है कि कचरा कलेक्टर को हाथ से कैसे ट्रिगर किया जाए, और भंडारण लीक की समस्या।
अंतिम रूप
एक जावा ऑब्जेक्ट एक finalize
विधि घोषित कर सकता है। जावा को ऑब्जेक्ट के लिए मेमोरी जारी करने से ठीक पहले इस विधि को कहा जाता है। यह आमतौर पर इस तरह दिखेगा:
public class MyClass {
//Methods for the class
@Override
protected void finalize() throws Throwable {
// Cleanup code
}
}
हालांकि, जावा फाइनल करने के व्यवहार पर कुछ महत्वपूर्ण चेतावनी दी गई है।
- जावा इस बारे में कोई गारंटी नहीं देता है कि
finalize()
विधि कब कहा जाएगा। - जावा भी गारंटी नहीं देता है कि चल रहे एप्लिकेशन के जीवनकाल के दौरान कुछ समय के लिए
finalize()
पद्धति को बुलाया जाएगा। - गारंटी वाली एकमात्र बात यह है कि ऑब्जेक्ट हटाए जाने से पहले विधि को कॉल किया जाएगा ... यदि ऑब्जेक्ट हटा दिया गया है।
ऊपर दिए गए कैविएट का अर्थ है कि क्लीनअप (या अन्य) क्रियाएं करने के लिए finalize
विधि पर भरोसा करना एक बुरा विचार है जिसे समय पर अंजाम देना चाहिए। अंतिम रूप से अधिक निर्भरता के कारण भंडारण लीक, मेमोरी लीक और अन्य समस्याएं हो सकती हैं।
संक्षेप में, बहुत कम ऐसी स्थिति होती है जहाँ अंतिम रूप देना वास्तव में एक अच्छा समाधान है।
फाइनल केवल एक बार चलते हैं
आम तौर पर, किसी ऑब्जेक्ट को अंतिम रूप दिए जाने के बाद हटा दिया जाता है। हालाँकि, ऐसा हर समय नहीं होता है। निम्नलिखित उदाहरण पर विचार करें 1 :
public class CaptainJack {
public static CaptainJack notDeadYet = null;
protected void finalize() {
// Resurrection!
notDeadYet = this;
}
}
जब CaptainJack
का एक उदाहरण CaptainJack
हो जाता है और कचरा संग्रहकर्ता इसे पुनः प्राप्त करने का प्रयास करता है, तो finalize()
विधि उदाहरण के लिए notDeadYet
चर का संदर्भ notDeadYet
। वह उदाहरण एक बार फिर पहुंच जाएगा, और कचरा संग्रहकर्ता उसे हटा नहीं देगा।
प्रश्न: क्या कप्तान जैक अमर है?
उत्तर: नहीं।
पकड़ यह है कि जेवीएम अपने जीवनकाल में केवल एक बार किसी वस्तु पर अंतिम रूप से चलेगा। यदि आप null
को notDeadYet
करने की अनुमति नहीं देते हैं, तो एक resurected उदाहरण के कारण एक बार फिर पहुंच से बाहर हो सकता है, कचरा संग्रहकर्ता ऑब्जेक्ट पर finalize()
को कॉल नहीं करेगा।
1 - देखें https://en.wikipedia.org/wiki/Jack_Harkness ।
मैन्युअल रूप से जीसी को ट्रिगर करना
आप कॉल करके मैन्युअल रूप से गारबेज कलेक्टर को ट्रिगर कर सकते हैं
System.gc();
हालांकि, जावा यह गारंटी नहीं देता है कि कॉल वापस आने पर कचरा कलेक्टर ने चलाया है। यह विधि बस जेवीएम (जावा वर्चुअल मशीन) को "सुझाव" देती है कि आप इसे कचरा कलेक्टर चलाना चाहते हैं, लेकिन ऐसा करने के लिए मजबूर नहीं करते हैं।
यह आमतौर पर कचरा संग्रह को मैन्युअल रूप से ट्रिगर करने का प्रयास करने के लिए एक बुरा अभ्यास माना जाता है। JVM को System.gc()
कॉल अक्षम करने के लिए -XX:+DisableExplicitGC
विकल्प के साथ चलाया जा सकता है। System.gc()
कॉल करके कचरा संग्रह को ट्रिगर करना सामान्य कचरा प्रबंधन / वस्तु संवर्धन गतिविधियों को जेवीएम द्वारा उपयोग में लेने से बाधित कर सकता है।
कचरा इकठा करना
C ++ दृष्टिकोण - नया और हटाएं
C ++ जैसी भाषा में, एप्लिकेशन प्रोग्राम डायनामिक रूप से आवंटित मेमोरी द्वारा उपयोग की जाने वाली मेमोरी के प्रबंधन के लिए जिम्मेदार है। जब new
ऑपरेटर का उपयोग करके C ++ हीप में कोई ऑब्जेक्ट बनाया जाता है, तो ऑब्जेक्ट को delete
के लिए delete
ऑपरेटर का एक समान उपयोग करने की आवश्यकता होती है:
यदि प्रोग्राम किसी ऑब्जेक्ट को
delete
लिए भूल जाता है और इसके बारे में सिर्फ "भूल जाता है", तो संबंधित मेमोरी एप्लिकेशन में खो जाती है। इस स्थिति के लिए शब्द एक स्मृति रिसाव है , और यह बहुत अधिक स्मृति लीक एक आवेदन अधिक से अधिक स्मृति का उपयोग करने के लिए उत्तरदायी है, और अंत में दुर्घटना।दूसरी ओर, यदि कोई एप्लिकेशन एक ही ऑब्जेक्ट को दो बार
delete
का प्रयास करता है, या किसी ऑब्जेक्ट को डिलीट करने के बाद उसका उपयोग करता है, तो एप्लिकेशन मेमोरी भ्रष्टाचार की समस्याओं के कारण क्रैश होने के लिए उत्तरदायी है
एक जटिल सी ++ प्रोग्राम में, new
और delete
का उपयोग करके मेमोरी मैनेजमेंट को लागू करना समय लेने वाला हो सकता है। दरअसल, स्मृति प्रबंधन बग का एक सामान्य स्रोत है।
जावा दृष्टिकोण - कचरा संग्रह
जावा एक अलग दृष्टिकोण लेता है। एक स्पष्ट delete
ऑपरेटर के बजाय, जावा एक स्वचालित तंत्र प्रदान करता है जिसे कचरा संग्रह के रूप में जाना जाता है जो उन वस्तुओं द्वारा उपयोग की जाने वाली मेमोरी को पुनः प्राप्त करने के लिए है जिनकी अब आवश्यकता नहीं है। जावा रनटाइम सिस्टम उन वस्तुओं को खोजने की जिम्मेदारी लेता है जिनका निपटान किया जाना है। यह कार्य एक घटक द्वारा किया जाता है जिसे कचरा संग्राहक , या संक्षेप में GC कहा जाता है।
किसी जावा प्रोग्राम के निष्पादन के दौरान, हम सभी मौजूदा वस्तुओं के सेट को दो अलग-अलग उप-भागों में विभाजित कर सकते हैं 1 :
प्रतिक्रियाशील वस्तुओं को जेएलएस द्वारा परिभाषित किया गया है:
एक पहुंच योग्य वस्तु वह वस्तु है जिसे किसी भी जीवित धागे से किसी भी संभावित निरंतर गणना में पहुँचा जा सकता है।
व्यवहार में, इसका मतलब है कि इन-स्कोप लोकल वैरिएबल या
static
वैरिएबल से शुरू होने वाले रेफरेंस की एक श्रंखला है, जिसके द्वारा कुछ कोड ऑब्जेक्ट तक पहुंचने में सक्षम हो सकता है।अगम्य वस्तुएँ वे वस्तुएँ हैं जिन्हें संभवतः ऊपर के रूप में नहीं पहुँचा जा सकता है।
किसी भी ऑब्जेक्ट पहुंचा नहीं जा सकता कचरा संग्रहण के लिए पात्र हैं। इसका मतलब यह नहीं है कि वे कचरा एकत्र किए जाएंगे । असल में:
- एक नहीं पहुंचा जा सकता वस्तु नहीं पहुंचा जा सकता 1 बनने पर तुरंत एकत्र न हो।
- एक अगम्य वस्तु कभी भी कचरा एकत्र नहीं हो सकती है ।
जावा भाषा की विशिष्टता एक JVM कार्यान्वयन के लिए बहुत से अक्षांश को यह तय करने के लिए देती है कि अप्राप्य वस्तुओं को कब एकत्रित किया जाए। यह भी (व्यवहार में) एक जेवीएम कार्यान्वयन के लिए अनुमति देता है कि यह रूढ़िवादी वस्तुओं का पता लगाने में कैसे रूढ़िवादी है।
जेएलएस गारंटी देता है कि एक बात यह है कि कोई भी पहुंच योग्य वस्तु कभी भी कचरा एकत्र नहीं किया जाएगा।
जब कोई वस्तु अप्राप्य हो जाती है तो क्या होता है
सबसे पहले, कुछ भी विशेष रूप से तब नहीं होता है जब कोई वस्तु अप्राप्य हो जाती है । चीजें केवल तब होती हैं जब कचरा कलेक्टर चलाता है और यह पता लगाता है कि ऑब्जेक्ट पहुंच से बाहर है। इसके अलावा, जीसी रन के लिए सभी पहुंच योग्य वस्तुओं का पता नहीं लगाना आम बात है।
जब जीसी एक अगम्य वस्तु का पता लगाता है, तो निम्नलिखित घटनाएं घट सकती हैं।
यदि कोई
Reference
ऑब्जेक्ट हैं जो ऑब्जेक्ट को संदर्भित करते हैं, तो ऑब्जेक्ट को हटाए जाने से पहले उन संदर्भों को साफ कर दिया जाएगा।यदि ऑब्जेक्ट अंतिम रूप देने योग्य है , तो इसे अंतिम रूप दिया जाएगा। ऑब्जेक्ट डिलीट होने से पहले ऐसा होता है।
ऑब्जेक्ट को हटाया जा सकता है, और यह जिस मेमोरी में व्याप्त है उसे पुनः प्राप्त किया जा सकता है।
ध्यान दें कि एक स्पष्ट अनुक्रम है जिसमें उपरोक्त घटनाएं हो सकती हैं, लेकिन किसी भी चीज़ को किसी विशिष्ट समय-सीमा में किसी विशिष्ट वस्तु को अंतिम हटाने के लिए कचरा कलेक्टर की आवश्यकता नहीं है।
उपलब्ध और अनुपलब्ध वस्तुओं के उदाहरण
निम्नलिखित उदाहरण वर्गों पर विचार करें:
// A node in simple "open" linked-list.
public class Node {
private static int counter = 0;
public int nodeNumber = ++counter;
public Node next;
}
public class ListTest {
public static void main(String[] args) {
test(); // M1
System.out.prinln("Done"); // M2
}
private static void test() {
Node n1 = new Node(); // T1
Node n2 = new Node(); // T2
Node n3 = new Node(); // T3
n1.next = n2; // T4
n2 = null; // T5
n3 = null; // T6
}
}
आइए देखें कि जब test()
कहा जाता है तो क्या होता है। कथन T1, T2 और T3 Node
ऑब्जेक्ट बनाते हैं, और ऑब्जेक्ट क्रमशः n1
, n2
और n3
वेरिएबल्स के माध्यम से उपलब्ध होते हैं। कथन T4, दूसरे Node
के संदर्भ को पहले वाले के next
क्षेत्र में निर्दिष्ट करता है। जब ऐसा किया जाता है, 2 Node
दो रास्तों के माध्यम से उपलब्ध होता है:
n2 -> Node2
n1 -> Node1, Node1.next -> Node2
बयान T5 में, हम असाइन null
करने के लिए n2
। यह Node2
लिए Node2
चेन की पहली को तोड़ता है, लेकिन दूसरा अखंड रहता है, इसलिए Node2
अभी भी पहुंच योग्य नहीं है।
बयान T6 में, हम असाइन null
करने के लिए n3
। यह Node3
लिए Node3
चेन को Node3
, जो Node3
अप्राप्य बनाता है। हालांकि, Node1
और Node2
दोनों अभी भी n1
चर के माध्यम से उपलब्ध हैं।
अंत में, जब test()
विधि वापस आती है, तो इसके स्थानीय चर n1
, n2
और n3
कार्यक्षेत्र से बाहर हो जाते हैं, और इसलिए इसे किसी भी चीज से एक्सेस नहीं किया जा सकता है। यह Node1
और Node2
लिए शेष रीचैबिलिटी चेन को तोड़ता है, और सभी Node
ऑब्जेक्ट Node2
हैं और न ही कचरा संग्रहण के लिए पात्र हैं ।
1 - यह एक सरलीकरण है जो अंतिम रूप देने और Reference
वर्गों की उपेक्षा करता है। 2 - Hypothetically, एक Java कार्यान्वयन ऐसा कर सकता है, लेकिन इसे करने की प्रदर्शन लागत इसे अव्यावहारिक बना देती है।
ढेर, पर्मगेन और स्टैक के आकार निर्धारित करना
जब जावा वर्चुअल मशीन शुरू होती है, तो यह जानना आवश्यक है कि ढेर बनाने के लिए कितना बड़ा है, और थ्रेड स्टैक के लिए डिफ़ॉल्ट आकार। इन्हें java
कमांड पर कमांड-लाइन विकल्पों का उपयोग करके निर्दिष्ट किया जा सकता है। जावा 8 के पूर्व के संस्करणों के लिए, आप ढेर के पर्मगेन क्षेत्र के आकार को भी निर्दिष्ट कर सकते हैं।
ध्यान दें कि जावा 8 में PermGen को हटा दिया गया था, और यदि आप PermGen आकार को सेट करने का प्रयास करते हैं, तो विकल्प को अनदेखा किया जाएगा (चेतावनी संदेश के साथ)।
यदि आप स्पष्ट रूप से हीप और स्टैक आकार निर्दिष्ट नहीं करते हैं, तो JVM उन डिफॉल्ट्स का उपयोग करेगा, जिनकी गणना किसी संस्करण और प्लेटफ़ॉर्म विशिष्ट तरीके से की जाती है। यह आपके अनुप्रयोग में बहुत कम या बहुत अधिक मेमोरी का उपयोग कर सकता है। यह आमतौर पर थ्रेड स्टैक के लिए ठीक है, लेकिन यह प्रोग्राम के लिए समस्याग्रस्त हो सकता है जो बहुत सारी मेमोरी का उपयोग करता है।
ढेर, पर्मगेन और डिफ़ॉल्ट स्टैक साइज़ सेट करना:
निम्न JVM विकल्प ढेर का आकार निर्धारित करते हैं:
-
-Xms<size>
- प्रारंभिक ढेर का आकार निर्धारित करता है -
-Xmx<size>
- अधिकतम ढेर आकार सेट करता है -
-XX:PermSize<size>
- प्रारंभिक PermGen आकार सेट करता है -
-XX:MaxPermSize<size>
- अधिकतम PermGen आकार सेट करता है -
-Xss<size>
- डिफ़ॉल्ट थ्रेड स्टैक आकार सेट करता है
<size>
पैरामीटर कई बाइट्स हो सकता है, या k
, m
या g
का प्रत्यय हो सकता है। उत्तरार्द्ध क्रमशः किलोबाइट्स, मेगाबाइट्स और गीगाबाइट्स में आकार निर्दिष्ट करते हैं।
उदाहरण:
$ java -Xms512m -Xmx1024m JavaApp
$ java -XX:PermSize=64m -XX:MaxPermSize=128m JavaApp
$ java -Xss512k JavaApp
डिफ़ॉल्ट आकार ढूँढना:
-XX:+printFlagsFinal
शुरू करने से पहले सभी झंडे के मूल्यों को प्रिंट करने के लिए -XX:+printFlagsFinal
विकल्प का उपयोग किया जा सकता है। इसका उपयोग ढेर और स्टैक आकार सेटिंग्स के लिए डिफॉल्ट्स को प्रिंट करने के लिए किया जा सकता है:
लिनक्स के लिए, यूनिक्स, सोलारिस और मैक ओएसएक्स
$ java -XX: + PrintFlagsFinal -version | grep -iE 'HeapSize | PermSize | ThreadStackSize'
विंडोज के लिए:
java -XX: + PrintFlagsFinal -version | पनाह / मैं "HeapSize PermSize ThreadStackSize"
उपरोक्त आदेशों का आउटपुट निम्न के जैसा होगा:
uintx InitialHeapSize := 20655360 {product}
uintx MaxHeapSize := 331350016 {product}
uintx PermSize = 21757952 {pd product}
uintx MaxPermSize = 85983232 {pd product}
intx ThreadStackSize = 1024 {pd product}
आकार बाइट्स में दिए गए हैं।
जावा में मेमोरी लीक
कचरा संग्रह उदाहरण में, हमने अनुमान लगाया है कि जावा मेमोरी लीक की समस्या को हल करता है। यह वास्तव में सच नहीं है। एक जावा प्रोग्राम मेमोरी को लीक कर सकता है, हालांकि लीक के कारण अलग-अलग हैं।
रिएक्टिव ऑब्जेक्ट्स लीक हो सकते हैं
निम्नलिखित भोले स्टैक कार्यान्वयन पर विचार करें।
public class NaiveStack {
private Object[] stack = new Object[100];
private int top = 0;
public void push(Object obj) {
if (top >= stack.length) {
throw new StackException("stack overflow");
}
stack[top++] = obj;
}
public Object pop() {
if (top <= 0) {
throw new StackException("stack underflow");
}
return stack[--top];
}
public boolean isEmpty() {
return top == 0;
}
}
जब आप किसी ऑब्जेक्ट को push
और फिर उसे तुरंत pop
करते हैं, तब भी stack
ऐरे में ऑब्जेक्ट का संदर्भ होगा।
स्टैक कार्यान्वयन के तर्क का अर्थ है कि संदर्भ को एपीआई के एक ग्राहक को वापस नहीं किया जा सकता है। यदि किसी वस्तु को पॉप किया गया है तो हम यह साबित कर सकते हैं कि इसे "किसी भी जीवित धागे से किसी भी संभावित निरंतर अभिकलन में पहुँचा नहीं जा सकता है" । समस्या यह है कि वर्तमान पीढ़ी जेवीएम यह साबित नहीं कर सकती है। वर्तमान पीढ़ी जेवीएम यह निर्धारित करने में कार्यक्रम के तर्क पर विचार नहीं करती है कि क्या संदर्भ उपलब्ध हैं। (एक शुरुआत के लिए, यह व्यावहारिक नहीं है।)
लेकिन वास्तव में क्या पुनरावृत्ति का मतलब है, के मुद्दे को अलग करते हुए, हमारे पास स्पष्ट रूप से एक ऐसी स्थिति है जहां NaiveStack
कार्यान्वयन "लटकी हुई" वस्तुओं पर है जिन्हें पुनः प्राप्त किया जाना चाहिए। यह एक स्मृति रिसाव है।
इस मामले में, समाधान सीधा है:
public Object pop() {
if (top <= 0) {
throw new StackException("stack underflow");
}
Object popped = stack[--top];
stack[top] = null; // Overwrite popped reference with null.
return popped;
}
कैश मेमोरी लीक हो सकता है
सेवा प्रदर्शन को बेहतर बनाने के लिए एक सामान्य रणनीति परिणामों को कैश करना है। विचार यह है कि आप कैश के रूप में जाने वाले इन-मेमोरी डेटा संरचना में आम अनुरोधों और उनके परिणामों का रिकॉर्ड रखते हैं। फिर, हर बार एक अनुरोध किया जाता है, आप कैश में अनुरोध को देखते हैं। यदि लुकअप सफल होता है, तो आप संबंधित सहेजे गए परिणाम लौटाते हैं।
इसे सही तरीके से लागू किया जाए तो यह रणनीति बहुत प्रभावी हो सकती है। हालांकि, अगर गलत तरीके से लागू किया जाता है, तो कैश मेमोरी लीक हो सकता है। निम्नलिखित उदाहरण पर विचार करें:
public class RequestHandler {
private Map<Task, Result> cache = new HashMap<>();
public Result doRequest(Task task) {
Result result = cache.get(task);
if (result == null) {
result == doRequestProcessing(task);
cache.put(task, result);
}
return result;
}
}
इस कोड के साथ समस्या यह है कि जबकि doRequest
कोई भी कॉल कैश में एक नई प्रविष्टि जोड़ सकता है, उन्हें हटाने के लिए कुछ भी नहीं है। यदि सेवा लगातार अलग-अलग कार्य कर रही है, तो कैश अंततः सभी उपलब्ध मेमोरी का उपभोग करेगा। यह स्मृति रिसाव का एक रूप है।
इसे हल करने के लिए एक दृष्टिकोण अधिकतम आकार के साथ कैश का उपयोग करना है, और कैश से अधिक होने पर पुरानी प्रविष्टियों को फेंकना है। (कम से कम हाल ही में उपयोग की गई प्रविष्टि को फेंकना एक अच्छी रणनीति है।) एक और तरीका यह है कि WeakHashMap
का उपयोग करके कैश का निर्माण किया जाए, ताकि यदि ढेर बहुत अधिक होने लगे तो JVM कैश प्रविष्टियों को निकाल सकता है।