क्या होता है जब OutOfMemoryError को फेंकने के लिए अपर्याप्त मेमोरी होती है?


207

मुझे पता है कि प्रत्येक वस्तु को ढेर मेमोरी की आवश्यकता होती है और स्टैक पर प्रत्येक आदिम / संदर्भ को स्टैक मेमोरी की आवश्यकता होती है।

जब मैं ढेर पर एक ऑब्जेक्ट बनाने का प्रयास करता हूं और ऐसा करने के लिए अपर्याप्त मेमोरी होती है, तो JVM ढेर पर एक java.lang.OutOfMemoryError बनाता है और मुझे फेंकता है।

तो जाहिर है, इसका मतलब है कि स्टार्टअप पर JVM द्वारा आरक्षित कुछ मेमोरी है।

क्या होता है जब इस आरक्षित मेमोरी का उपयोग किया जाता है (यह निश्चित रूप से उपयोग किया जाएगा, नीचे चर्चा पढ़ें) और JVM के पास java.lang.OutOfMemoryError का एक उदाहरण बनाने के लिए ढेर पर पर्याप्त मेमोरी नहीं है ?

क्या यह सिर्फ लटका है? या वह मुझे फेंक देगा nullक्योंकि newOOM के उदाहरण के लिए कोई स्मृति नहीं है ?

try {
    Object o = new Object();
    // and operations which require memory (well.. that's like everything)
} catch (java.lang.OutOfMemoryError e) {
    // JVM had insufficient memory to create an instance of java.lang.OutOfMemoryError to throw to us
    // what next? hangs here, stuck forever?
    // or would the machine decide to throw us a "null" ? (since it doesn't have memory to throw us anything more useful than a null)
    e.printStackTrace(); // e.printStackTrace() requires memory too.. =X
}

==

जेवीएम पर्याप्त मेमोरी क्यों नहीं रख सका?

कोई भी मेमोरी कितनी भी आरक्षित क्यों न हो, उस मेमोरी का उपयोग तब भी संभव है जब JVM के पास उस मेमोरी को "पुनः प्राप्त" करने का कोई तरीका न हो:

try {
    Object o = new Object();
} catch (java.lang.OutOfMemoryError e) {
    // JVM had 100 units of "spare memory". 1 is used to create this OOM.
    try {
        e.printStackTrace();
    } catch (java.lang.OutOfMemoryError e2) {
        // JVM had 99 units of "spare memory". 1 is used to create this OOM.
        try {
            e.printStackTrace();
        } catch (java.lang.OutOfMemoryError e3) {
            // JVM had 98 units of "spare memory". 1 is used to create this OOM.
            try {
                e.printStackTrace();
            } catch (java.lang.OutOfMemoryError e4) {
                // JVM had 97 units of "spare memory". 1 is used to create this OOM.
                try {
                    e.printStackTrace();
                } catch (java.lang.OutOfMemoryError e5) {
                    // JVM had 96 units of "spare memory". 1 is used to create this OOM.
                    try {
                        e.printStackTrace();
                    } catch (java.lang.OutOfMemoryError e6) {
                        // JVM had 95 units of "spare memory". 1 is used to create this OOM.
                        e.printStackTrace();
                        //........the JVM can't have infinite reserved memory, he's going to run out in the end
                    }
                }
            }
        }
    }
}

या अधिक संक्षेप में:

private void OnOOM(java.lang.OutOfMemoryError e) {
    try {
        e.printStackTrace();
    } catch (java.lang.OutOfMemoryError e2) {
        OnOOM(e2);
    }
}

2
आपका जवाब काफी हद तक JVM पर निर्भर होगा
MozenRath

23
एक टेलीफोनी लाइब्रेरी जो मैंने एक बार (90 के दशक में) का इस्तेमाल किया था OutOfMemoryExceptionऔर तब कुछ ऐसा किया था जिसमें एक बड़ा बफर बनाना शामिल था ...
टॉम हैटिन -

@ टॉमहॉटिन-टैक्लाइन क्या होगा अगर ऐसा करने में शामिल संचालन एक और OOM फेंकता है?
पचेरियर

38
सेलफोन की तरह, यह बैटरी से बाहर निकलता है, लेकिन इसमें स्पैमिंग "आप बैटरी से बाहर चल रहे हैं" रखने के लिए पर्याप्त बैटरी है।
काज़ुमा

1
"जब इस आरक्षित मेमोरी का उपयोग किया जाता है तो क्या होता है": यह तभी हो सकता है जब प्रोग्राम पहले पकड़ा गया हो OutOfMemoryErrorऔर इसके संदर्भ को बनाए रखा हो। यह ट्रांसपायर करता है कि एक OutOfMemoryErrorको पकड़ना उतना उपयोगी नहीं है जितना कोई सोच सकता है, क्योंकि आप इसे पकड़ने पर अपने कार्यक्रम की स्थिति के बारे में लगभग कुछ भी गारंटी नहीं दे सकते । देखें stackoverflow.com/questions/8728866/…
Raedwald

जवाबों:


145

जेवीएम वास्तव में स्मृति से बाहर नहीं है। यह पहले से ढेर ढेर की स्मृति गणना करता है।

जेवीएम की संरचना, अध्याय 3 , खंड 3.5.2 राज्य:

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

के लिए ढेर , धारा 3.5.3।

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

तो, यह ऑब्जेक्ट का आवंटन करने से पहले अग्रिम में एक संगणना करता है।


क्या होता है कि जेवीएम किसी ऑब्जेक्ट के लिए मेमोरी को परमानेंट जेनरेशन रीजन (या परमस्पेस) नामक मेमोरी के लिए आवंटित करने की कोशिश करता है। यदि आवंटन विफल हो जाता है (JVM द्वारा गारबेज कलेक्टर को आमंत्रित करने और मुक्त स्थान आवंटित करने के बाद भी), यह फेंकता है a OutOfMemoryError। यहां तक ​​कि अपवादों में एक मेमोरी स्पेस की आवश्यकता होती है, इसलिए त्रुटि अनिश्चित काल के लिए फेंक दी जाएगी।

आगे की पढाई। ? इसके अलावा, OutOfMemoryErrorविभिन्न JVM संरचना में हो सकता है


10
मेरा मतलब है हाँ, लेकिन क्या जावा वर्चुअल मशीन को भी आउटऑफमेमोरी एरर फेंकने के लिए मेमोरी की आवश्यकता नहीं होगी? क्या होता है जब OOM फेंकने के लिए कोई मेमोरी नहीं होती है?
पैशियर

5
लेकिन अगर JVM OOM के एक ही उदाहरण के संदर्भ को वापस नहीं करता है, तो क्या आप सहमत नहीं हैं कि अंततः यह आरक्षित मेमोरी से बाहर हो जाएगा? (जैसा कि प्रश्न में कोड में दिखाया गया है)
पचेरियर

1
मुझे ग्राहम की टिप्पणी का सन्दर्भ यहाँ देने की अनुमति दें: stackoverflow.com/questions/9261705/…
पचेरियर

2
अगर वीएम ओओएम अपवाद के एक सिंगलटन और परमाणु ऊर्जा संयंत्र में वीएम को बनाए रखता है तो अच्छा हो सकता है।
जॉन के

8
@ जॉन: मुझे उम्मीद है कि जावा में परमाणु ऊर्जा संयंत्रों को प्रोग्राम नहीं किया जाएगा, ठीक उसी तरह जैसे स्पेस शटल और बोइंग 757 को जावा में प्रोग्राम नहीं किया जाता है।
डेट्रिच एप

64

ग्राहम बोरलैंड को यह सही लगता है : कम से कम मेरा जेवीएम जाहिरा तौर पर आउटऑफमेरी एरर्स का फिर से उपयोग करता है। इसे जांचने के लिए, मैंने एक साधारण परीक्षण कार्यक्रम लिखा:

class OOMTest {
    private static void test (OutOfMemoryError o) {
        try {
            for (int n = 1; true; n += n) {
                int[] foo = new int[n];
            }
        } catch (OutOfMemoryError e) {
            if (e == o)
                System.out.println("Got the same OutOfMemoryError twice: " + e);
            else test(e);
        }
    }
    public static void main (String[] args) {
        test(null);
    }
}

इसे चलाने से यह उत्पादन होता है:

$ javac OOMTest.java && java -Xmx10m OOMTest 
Got the same OutOfMemoryError twice: java.lang.OutOfMemoryError: Java heap space

BTW, JVM मैं (Ubuntu 10.04 पर) चला रहा हूँ:

$ java -version
java version "1.6.0_26"
Java(TM) SE Runtime Environment (build 1.6.0_26-b03)
Java HotSpot(TM) 64-Bit Server VM (build 20.1-b02, mixed mode)

संपादित करें: मैंने यह देखने की कोशिश की कि अगर मैं होता तो क्या होता निम्नलिखित कार्यक्रम का उपयोग करके जेवीएम को पूरी तरह से मेमोरी से बाहर चलाने के लिए मजबूर कर दूं:

class OOMTest2 {
    private static void test (int n) {
        int[] foo;
        try {
            foo = new int[n];
            test(n * 2);
        }
        catch (OutOfMemoryError e) {
            test((n+1) / 2);
        }
    }
    public static void main (String[] args) {
        test(1);
    }
}

जैसा कि यह पता चला है, यह हमेशा के लिए लूप लगता है। हालाँकि, उत्सुकता से, प्रोग्राम को Ctrl+ के साथ समाप्त करने की कोशिश Cनहीं की जाती है, लेकिन केवल निम्न संदेश देता है:

Java HotSpot(TM) 64-Bit Server VM warning: Exception java.lang.OutOfMemoryError occurred dispatching signal SIGINT to handler- the VM may need to be forcibly terminated


अच्छा परीक्षण, मेरे लिए संस्करण "1.7.0_01" जावा हॉटस्पॉट (टीएम) 64-बिट सर्वर वीएम
पेसियर

दिलचस्प। ऐसा लगता है कि जेवीएम पूरी तरह से पूंछ पुनरावृत्ति को नहीं समझता है ... (जैसे कि यह कुछ पूंछ-पुनरावर्ती स्टैक का पुन: उपयोग कर रहा है, लेकिन सभी मेमोरी को साफ करने के लिए पर्याप्त नहीं है ...)
इज़काता

अपने कोड को संशोधित करके देखें कि मुझे कितने अलग-अलग विकल्प मिलते हैं - मुझे हमेशा एक और शाखा मिलती है जिसे ठीक 5 बार निष्पादित किया जाता है।
इरफी

@ इज़काटा: मैं यह कहूंगा कि यह nOOM को पहले से आवंटित करने और बाद में उनमें से एक का पुन: उपयोग करने के लिए एक सचेत निर्णय है , ताकि एक OOM हमेशा फेंका जा सके। सन / ओरेकल की JVM सभी IIRC पर टेल रीसर्शन का समर्थन नहीं करती है?
इरफी

10
@ इज़काटा लूप जाहिरा तौर पर अंतहीन चल रहा है क्योंकि जेवीएम लगातार एक और एक ही (5 वां या तो) ओओएम को स्मृति से बाहर चलाने के बाद फेंक रहा है। तो यह nढेर पर फ्रेम है और यह n+1अनंत काल के लिए फ्रेम बनाने और नष्ट करने को समाप्त करता है , जिससे अंतहीन रूप से चलने की उपस्थिति होती है।
इरफी

41

अधिकांश रनटाइम वातावरण स्टार्टअप पर पूर्व-आवंटित करेगा, या अन्यथा आरक्षित होगा, मेमोरी भुखमरी की स्थिति से निपटने के लिए पर्याप्त मेमोरी। मुझे लगता है कि ज्यादातर समझदार JVM कार्यान्वयन ऐसा करेंगे।


1
stackoverflow.com/questions/9261215/… : सही है लेकिन अगर JVM ने ऐसा करने के लिए मेमोरी की 100 इकाइयाँ आरक्षित की हैं, और पहली OOM पर 1 यूनिट खर्च की है, तो क्या होगा अगर मेरे OOM कैच ब्लॉक में मैं e.printStatTrace () करूँ? e.printStackTrace () में मेमोरी की भी आवश्यकता होती है। तब JVM मुझे एक और OOM (98 इकाइयाँ) छोड़ने के लिए मेमोरी की एक और इकाई खर्च करेगा और मैं इसे e.printStackTrace () के साथ पकड़ता हूँ, इसलिए JVM मुझे एक और OOM (97 इकाइयाँ) छोड़ता है और अच्छी तरह से पकड़ा जाता है और मैं चाहता था ..
पचेरियर

3
यही कारण है कि OOME कभी भी स्टैक ट्रेस शामिल करने के लिए उपयोग नहीं किया जाता है - स्टैक निशान मेमोरी लेते हैं! OOME ने केवल java 6 ( blogs.oracle.com/alanb/entry/… ) में स्टैक ट्रेस शामिल करने का प्रयास शुरू किया । मेरा मानना ​​है कि यदि स्टैक ट्रेस संभव नहीं है, तो स्टैक ट्रेस के बिना अपवाद को फेंक दिया जाता है।
सीन रीली

1
@ सीनियन का मतलब है कि एक अपवाद जिसका स्टैक ट्रेस नहीं है वह अभी भी एक ऑब्जेक्ट है, जिसे अभी भी मेमोरी की आवश्यकता है। स्टैक ट्रेस प्रदान किया गया है या नहीं, इसकी परवाह किए बिना मेमोरी की आवश्यकता है। क्या यह सच है कि कैच ब्लॉक में अगर OOM बनाने के लिए कोई मेमोरी नहीं बची है (स्टैक ट्रेस के बिना एक भी मेमोरी बनाने के लिए कोई मेमोरी नहीं है), तो मैं एक अशक्त को पकड़ लूंगा?
पचेरियर

17
JVM OOM अपवाद के एक एकल स्थिर उदाहरण के लिए कई संदर्भ वापस कर सकता है। तो भले ही आपकाcatch क्लॉज अधिक मेमोरी का उपयोग करने का प्रयास करता है, तो जेवीएम केवल एक ही ओओएम उदाहरण को बार-बार फेंक सकता है।
ग्राहम बोरलैंड

1
@ TheEliteGentleman मैं मानता हूं कि वे बहुत अच्छे उत्तर भी हैं, लेकिन JVM एक भौतिक मशीन पर रहता है, उन उत्तरों ने यह नहीं बताया कि कैसे JVM जादुई रूप से OOM का एक उदाहरण प्रदान करने के लिए पर्याप्त स्मृति रख सकता है। "यह हमेशा एक ही उदाहरण है" पहेली को हल करने के लिए लगता है।
15

23

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


12

JVM युक्ति, अध्याय 3.5.2 से:

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

हर जावा वर्चुअल मशीन को यह गारंटी देनी होती है कि यह एक फेंक देगा OutOfMemoryError । इसका तात्पर्य यह है कि इसका एक उदाहरण बनाने में सक्षम होना चाहिएOutOfMemoryError (या अग्रिम में बनाया गया है) भले ही कोई खाली स्थान न बचा हो।

हालांकि इसकी गारंटी नहीं है, कि इसे पकड़ने और एक अच्छा स्टैकट्रेस प्रिंट करने के लिए पर्याप्त मेमोरी बाकी है ...

इसके अलावा

आपने यह दिखाने के लिए कुछ कोड जोड़ा है कि JVM ढेर जगह से निकल सकता है अगर उसे एक से अधिक फेंकना पड़े OutOfMemoryError । लेकिन ऐसा कार्यान्वयन ऊपर से आवश्यकता का उल्लंघन करेगा।

इस बात की कोई आवश्यकता नहीं है कि फेंके गए उदाहरण OutOfMemoryErrorअद्वितीय हैं या मांग पर बनाए गए हैं। एक JVM OutOfMemoryErrorस्टार्टअप के दौरान ठीक एक उदाहरण तैयार कर सकता है और जब भी यह ढेर स्थान से बाहर निकलता है - जो कि एक बार सामान्य वातावरण में होता है, तब इसे फेंक देता है। दूसरे शब्दों में: इसका उदाहरण OutOfMemoryErrorजो हम देखते हैं वह एक सिंगलटन हो सकता है।


मुझे लगता है कि इसे लागू करने के लिए यह स्टैक-ट्रेस रिकॉर्डिंग से बचना होगा, अगर जगह तंग थी।
Raedwald

@Raedwald: वास्तव में, यह सिर्फ Oracle VM करता है, मेरा उत्तर देखें।
स्लेसके

11

दिलचस्प सवाल :-)। जबकि अन्य ने सैद्धांतिक पहलुओं की अच्छी व्याख्या दी है, मैंने इसे आज़माने का फैसला किया। यह Oracle JDK 1.6.0_26, विंडोज 7 64 बिट पर है।

परीक्षण व्यवस्था

मैंने मेमोरी को समाप्त करने के लिए एक सरल प्रोग्राम लिखा (नीचे देखें)।

कार्यक्रम सिर्फ एक स्थैतिक बनाता है java.util.List , और इसमें ताजा तार भरता रहता है, जब तक कि ओओएम फेंका नहीं जाता है। यह तब इसे पकड़ता है और एक अंतहीन लूप (खराब जेवीएम ...) में भराई जारी रखता है।

परीक्षा परिणाम

जैसा कि कोई आउटपुट से देख सकता है, पहले चार बार OOME फेंक दिया जाता है, यह स्टैक ट्रेस के साथ आता है। उसके बाद, बाद में OOME केवल प्रिंट करते हैं java.lang.OutOfMemoryError: Java heap spaceयदिprintStackTrace() किया जाता है, इसे लागू किया जाता है।

तो जाहिरा तौर पर जेवीएम एक स्टैक ट्रेस को प्रिंट करने का प्रयास करता है यदि यह हो सकता है, लेकिन अगर स्मृति वास्तव में तंग है, तो यह ट्रेस को छोड़ देता है, जैसे अन्य उत्तर सुझाते हैं।

इसके अलावा दिलचस्प OOME का हैश कोड है। ध्यान दें कि पहले कुछ OOME सभी में अलग-अलग हैश होते हैं। एक बार जब जेवीएम स्टैक निशान को छोड़ना शुरू कर देता है, हैश हमेशा समान होता है। इससे पता चलता है कि JVM जब तक संभव हो, ताजा (उपदेशात्मक?) कुछ उदाहरणों का उपयोग करेगा, लेकिन अगर धक्का को धक्का देना आता है, तो यह केवल फेंकने के लिए कुछ भी नहीं होने के बजाय उसी उदाहरण का पुन: उपयोग करेगा।

उत्पादन

नोट: मैंने आउटपुट को पढ़ने में आसान बनाने के लिए कुछ स्टैक के निशान को काट दिया ("[...]")।

iteration 0
iteration 100000
iteration 200000
iteration 300000
iteration 400000
iteration 500000
iteration 600000
iteration 700000
iteration 800000
iteration 900000
iteration 1000000
iteration 1100000
iteration 1200000
iteration 1300000
iteration 1400000
iteration 1500000
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1069480624
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.ArrayList.ensureCapacity(Unknown Source)
    at java.util.ArrayList.add(Unknown Source)
    at testsl.Div.gobbleUpMemory(Div.java:23)
    at testsl.Div.exhaustMemory(Div.java:12)
    at testsl.Div.main(Div.java:7)
java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
[...]
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 616699029
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
[...]
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 2136955031
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
[...]
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1535562945
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
Keep on trying...
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
Keep on trying...
[...]

कार्यक्रम

public class Div{
    static java.util.List<String> list = new java.util.ArrayList<String>();

    public static void main(String[] args) {
        exhaustMemory();
    }

    private static void exhaustMemory() {
        try {
            gobbleUpMemory();
        } catch (OutOfMemoryError e) {
            System.out.println("Ouch: " + e+"; hash: "+e.hashCode());
            e.printStackTrace();
            System.out.println("Keep on trying...");
            exhaustMemory();
        }
    }

    private static void gobbleUpMemory() {
        for (int i = 0; i < 10000000; i++) {
            list.add(new String("some random long string; use constructor to force new instance"));
            if (i % 10000000== 0) {
                System.out.println("iteration "+i);
            }
        }

    }
}

जब धक्का को धक्का लगता है, तो यह OOME के ​​लिए मेमोरी आवंटित नहीं कर सकता है, इसलिए यह पहले से ही बनाए गए फ्लश करता है।
बुहके सिंडी

1
माइनर नोट: आपके कुछ आउटपुट अनुक्रम से बाहर प्रतीत होते हैं, संभवतः क्योंकि आप मुद्रण कर रहे हैं System.outलेकिन डिफ़ॉल्ट रूप से printStackTrace()उपयोग करते हैं System.err। आप लगातार स्ट्रीम का उपयोग करके बेहतर परिणाम प्राप्त कर सकते हैं।
इल्मरी करोनें

@ इल्मारिकारोन: हां, मैंने उस पर गौर किया। यह केवल एक उदाहरण है, इसलिए यह कोई फर्क नहीं पड़ा। जाहिर है आप इसका उपयोग उत्पादन कोड में नहीं करेंगे।
१२:०२

यह सच है, मुझे यह पता लगाने में थोड़ा समय लगा कि जब मैंने पहली बार आउटपुट पर देखा तो **** क्या था।
इल्मरी करोनें

6

मुझे पूरा यकीन है, JVM इस बात को पूरी तरह सुनिश्चित करेगा कि मेमोरी से बाहर होने से पहले उसके पास अपवाद को फेंकने के लिए कम से कम पर्याप्त मेमोरी हो।


1
लेकिन वे इस स्थिति से
टकराएंगे

4

इस मामले में JVM, इस स्थिति के रनटाइम द्वारा प्रबंधित-मेमोरी वातावरण की सीमाओं का उल्लंघन करने का प्रयास करने वाले अपवादों को नियंत्रित करता है। JVM अपनी स्वयं की प्रक्रिया है, जो आपके एप्लिकेशन का IL चला रही है। एक प्रोग्राम को कॉल करने का प्रयास करना चाहिए जो कॉल स्टैक को सीमा से परे बढ़ाता है, या जेवीएम की तुलना में अधिक मेमोरी आवंटित कर सकता है, रनटाइम खुद एक अपवाद को इंजेक्ट करेगा, जिससे कॉल स्टैक अनकाउंड हो जाएगा। आपके प्रोग्राम को वर्तमान में कितनी मेमोरी की आवश्यकता है, या इसकी कॉल स्टैक कितनी गहरी है, इसके बावजूद, JVM ने उक्त अपवाद बनाने और इसे अपने कोड में इंजेक्ट करने के लिए अपनी स्वयं की प्रक्रिया सीमा के भीतर पर्याप्त मेमोरी आवंटित की होगी।


"जेवीएम ने उक्त अपवाद बनाने के लिए अपनी स्वयं की प्रक्रिया सीमा के भीतर पर्याप्त मेमोरी आवंटित की होगी" लेकिन अगर आपका कोड उस अपवाद का संदर्भ रखता है, तो इसका पुन: उपयोग नहीं किया जा सकता है, यह दूसरा कैसे बना सकता है? या आप यह सुझाव दे रहे हैं कि इसमें एक विशेष सिंगलटन ओओएम ऑब्जेक्ट है?
राधावल्द

मैं यह सुझाव नहीं दे रहा हूं। यदि आपका प्रोग्राम कभी भी बनाए गए हर अपवाद पर फँसता है और लटकता है, जिसमें JVM या OS द्वारा निर्मित और इंजेक्ट किया गया है, तो अंततः JVM स्वयं OS द्वारा निर्धारित कुछ सीमा से अधिक हो जाएगा, और OS इसे GPF या के लिए बंद कर देगा इसी तरह की त्रुटि। हालांकि, यह पहली जगह में खराब डिजाइन है; अपवादों को या तो संभाला जाना चाहिए और फिर दायरे से बाहर जाना चाहिए, या बाहर फेंक दिया जाना चाहिए। और आपको SOE या OOME पर पकड़ने और जारी रखने का प्रयास करना चाहिए; "सफाई" के अलावा आप शान से बाहर निकल सकते हैं, उन स्थितियों में निष्पादन जारी रखने के लिए आप कुछ नहीं कर सकते।
कीथ

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

4

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

आगे की पढाई:

और अंतिम नोट के रूप में, पकड़ने की कोशिश कर रहा है एक java.lang.Error (और इसके वंशज कक्षाएं) एक स्टैच्रेस प्रिंट करने के लिए आपको कोई उपयोगी जानकारी नहीं दे सकता है। आप इसके बजाय एक ढेर डंप चाहते हैं।


4

@Graham Borland के उत्तर को स्पष्ट करने के लिए, कार्यात्मक रूप से, JVM स्टार्टअप पर यह करता है:

private static final OutOfMemoryError OOME = new OutOfMemoryError();

बाद में, जेवीएम निम्नलिखित में से एक जावा बाईटेकोड्स को निष्पादित करता है: 'नया', 'एवरैरे', या 'मल्टीअनवरैरे'। यह निर्देश JVM को मेमोरी स्थिति से बाहर कई चरणों में प्रदर्शन करने का कारण बनता है:

  1. एक देशी समारोह का आह्वान, कहते हैं allocate()allocate()किसी विशेष वर्ग या सरणी के कुछ नए उदाहरण के लिए स्मृति आवंटित करने का प्रयास।
  2. यह आवंटन अनुरोध विफल हो जाता है, इसलिए जेवीएम एक और मूल कार्य को आमंत्रित करता है, कहते हैं doGC(), जो कचरा संग्रह करने का प्रयास करता है।
  3. जब वह फ़ंक्शन वापस आता है, allocate() एक बार फिर से उदाहरण के लिए मेमोरी आवंटित करने का प्रयास करता है।
  4. यदि वह विफल रहता है (*), तो JVM, आवंटित () के भीतर, बस एक है throw OOME;, जो OOME का जिक्र करता है कि यह स्टार्टअप पर त्वरित है। ध्यान दें कि उस OOME को आवंटित करने की आवश्यकता नहीं थी, यह सिर्फ इसे संदर्भित करता है।

जाहिर है, ये शाब्दिक चरण नहीं हैं; वे कार्यान्वयन में JVM से JVM तक भिन्न होंगे, लेकिन यह उच्च-स्तरीय विचार है।

(*) एक महत्वपूर्ण मात्रा में काम यहां विफल होने से पहले होता है। जेवीएम सॉफ्टेप्रिंट्स ऑब्जेक्ट्स को क्लियर करने का प्रयास करेगा, जेनरेशनल कलेक्टर का उपयोग करते समय सीधे टेन्योर पीढ़ी में आवंटन का प्रयास करेगा, और अंतिम रूप से संभवतः अन्य चीजें भी।


3

जवाब है कि कहते हैं कि JVM पूर्व आवंटित करेगा OutOfMemoryErrorsवास्तव में सही हैं।
एक आउट-ऑफ-मेमोरी स्थिति को उकसाकर इसका परीक्षण करने के अलावा, हम किसी भी JVM के ढेर को जांच सकते हैं (मैंने एक छोटे प्रोग्राम का उपयोग किया है जो सिर्फ एक नींद करता है, इसे जावा 8 अपडेट 31 से Oracle के हॉटस्पॉट JVM का उपयोग करके चलाता है)।

उपयोग करते हुए jmapहम देखते हैं कि OutOfMemoryError के 9 उदाहरण हैं (भले ही हमारे पास बहुत सी मेमोरी हो):

> jmap -histo 12103 | grep OutOfMemoryError
 71: 9 288 java.lang.OutOfMemoryError
170: 1 32 [Ljava.lang.OutOfMemoryError;

हम तब ढेर डंप उत्पन्न कर सकते हैं:

> jmap -dump: प्रारूप = b, फ़ाइल = heap.hprof 12315

और इसे ग्रहण मेमोरी एनालाइज़र का उपयोग करके खोलें , जहां एक OQL क्वेरी से पता चलता है कि JVM वास्तव में OutOfMemoryErrorsसभी संभावित संदेशों के लिए पूर्व-आबंटित लगता है :

यहां छवि विवरण दर्ज करें

जावा 8 हॉटस्पॉट जेवीएम के लिए कोड जो वास्तव में इनका प्रचार करता है , यहां पाया जा सकता है , और इस तरह दिखता है (कुछ भागों के साथ छोड़ दिया गया है):

...
// Setup preallocated OutOfMemoryError errors
k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_OutOfMemoryError(), true, CHECK_false);
k_h = instanceKlassHandle(THREAD, k);
Universe::_out_of_memory_error_java_heap = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_metaspace = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_class_metaspace = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_array_size = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_gc_overhead_limit =
  k_h->allocate_instance(CHECK_false);

...

if (!DumpSharedSpaces) {
  // These are the only Java fields that are currently set during shared space dumping.
  // We prefer to not handle this generally, so we always reinitialize these detail messages.
  Handle msg = java_lang_String::create_from_str("Java heap space", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_java_heap, msg());

  msg = java_lang_String::create_from_str("Metaspace", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_metaspace, msg());
  msg = java_lang_String::create_from_str("Compressed class space", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_class_metaspace, msg());

  msg = java_lang_String::create_from_str("Requested array size exceeds VM limit", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_array_size, msg());

  msg = java_lang_String::create_from_str("GC overhead limit exceeded", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_gc_overhead_limit, msg());

  msg = java_lang_String::create_from_str("/ by zero", CHECK_false);
  java_lang_Throwable::set_message(Universe::_arithmetic_exception_instance, msg());

  // Setup the array of errors that have preallocated backtrace
  k = Universe::_out_of_memory_error_java_heap->klass();
  assert(k->name() == vmSymbols::java_lang_OutOfMemoryError(), "should be out of memory error");
  k_h = instanceKlassHandle(THREAD, k);

  int len = (StackTraceInThrowable) ? (int)PreallocatedOutOfMemoryErrorCount : 0;
  Universe::_preallocated_out_of_memory_error_array = oopFactory::new_objArray(k_h(), len, CHECK_false);
  for (int i=0; i<len; i++) {
    oop err = k_h->allocate_instance(CHECK_false);
    Handle err_h = Handle(THREAD, err);
    java_lang_Throwable::allocate_backtrace(err_h, CHECK_false);
    Universe::preallocated_out_of_memory_errors()->obj_at_put(i, err_h());
  }
  Universe::_preallocated_out_of_memory_error_avail_count = (jint)len;
}
...

और यह कोड बताता है कि JVM पहले स्टैक ट्रेस के लिए पहले से आवंटित त्रुटियों में से किसी एक का उपयोग करने की कोशिश करेगा, और फिर स्टैक ट्रेस के बिना एक पर वापस गिर जाएगा:

oop Universe::gen_out_of_memory_error(oop default_err) {
  // generate an out of memory error:
  // - if there is a preallocated error with backtrace available then return it wth
  //   a filled in stack trace.
  // - if there are no preallocated errors with backtrace available then return
  //   an error without backtrace.
  int next;
  if (_preallocated_out_of_memory_error_avail_count > 0) {
    next = (int)Atomic::add(-1, &_preallocated_out_of_memory_error_avail_count);
    assert(next < (int)PreallocatedOutOfMemoryErrorCount, "avail count is corrupt");
  } else {
    next = -1;
  }
  if (next < 0) {
    // all preallocated errors have been used.
    // return default
    return default_err;
  } else {
    // get the error object at the slot and set set it to NULL so that the
    // array isn't keeping it alive anymore.
    oop exc = preallocated_out_of_memory_errors()->obj_at(next);
    assert(exc != NULL, "slot has been used already");
    preallocated_out_of_memory_errors()->obj_at_put(next, NULL);

    // use the message from the default error
    oop msg = java_lang_Throwable::message(default_err);
    assert(msg != NULL, "no message");
    java_lang_Throwable::set_message(exc, msg);

    // populate the stack trace and return it.
    java_lang_Throwable::fill_in_stack_trace_of_preallocated_backtrace(exc);
    return exc;
  }
}

अच्छा पोस्ट, मैं पिछले उत्तर पर वापस लौटने से पहले इसे अधिक दृश्यता देने के लिए एक सप्ताह के उत्तर के रूप में स्वीकार करूंगा।
पेसियर

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

@BuhakeSindi - मैं यह नहीं देखता कि परमानेंट जेनरेशन का इससे क्या लेना-देना है। नई वस्तुओं को स्थायी पीढ़ी में आवंटित नहीं किया जाता है जैसा कि आप अपने उत्तर में बताते हैं। आप इस तथ्य का भी कभी उल्लेख नहीं करते हैं कि आउटऑफ़मेरीइरियर्स पूर्व-आवंटित हैं (जो प्रश्न का वास्तविक उत्तर है)।
जोहान काविंग

1
ठीक है, मैं जो कह रहा हूं वह यह है कि जावा 8 से, ऑब्जेक्ट आवंटन को गतिशील रूप से गणना की जाती है, जबकि पहले इसे पूर्वनिर्धारित हीप स्थान पर आवंटित किया गया था, इसलिए शायद यह पहले से पूर्व-आवंटित है। हालांकि OOME पूर्व-आबंटित है, फिर भी "गणना" यह निर्धारित करने के लिए की गई है कि OOME को फेंकने की आवश्यकता है (इसलिए मैं जेएलएस विनिर्देश के संदर्भ में क्यों डालूं)।
बुहके सिंडी

1
जावा हीप का आकार जावा 8 में पहले जैसा ही पूर्वनिर्धारित है। परमानेंट जनरेशन हीप का हिस्सा था जिसमें क्लास मेटा-डेटा, इंटर्न स्ट्रिंग्स और क्लास स्टैटिक्स शामिल थे। इसका एक सीमित आकार था जिसे कुल ढेर आकार से अलग से ट्यून करने की आवश्यकता थी। जावा 8 में वर्ग मेटा-डेटा को देशी मेमोरी और इंटर्न स्ट्रिंग्स में ले जाया गया है और क्लास स्टेटिक्स को नियमित जावा हीप में ले जाया गया है (उदाहरण के लिए देखें: infoq.com/articles/Java-PERMGEN-Removed )।
जोहान काविंग
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.