java - क्या होता है जब आउटऑफमेमरी एरर फेंकने के लिए अपर्याप्त स्मृति होती है?




language-agnostic (8)

@ ग्राहम बोर्लैंड के उत्तर को और स्पष्ट करने के लिए, कार्यात्मक रूप से, JVM स्टार्टअप पर ऐसा करता है:

private static final OutOfMemoryError OOME = new OutOfMemoryError();

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

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

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

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

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

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

तो निश्चित रूप से, इसका मतलब है कि स्टार्टअप पर JVM द्वारा आरक्षित कुछ स्मृति है।

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

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

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);
    }
}

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


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


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

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

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

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

> jmap -dump:format=b,file=heap.hprof 12315

और इसे एक्लिप्स मेमोरी विश्लेषक का उपयोग करके खोलें, जहां एक ओक्यूएल क्वेरी से पता चलता है कि जेवीएम वास्तव में सभी संभावित संदेशों के लिए आउटऑफमेमरी एरर को पूर्व-आवंटित करता है:

जावा 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;
  }
}

जेवीएम स्पेक से, अध्याय 3.5.2:

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

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

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

इसके अलावा

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

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


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

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

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

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

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

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

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

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);
            }
        }

    }
}

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


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

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

बीटीडब्ल्यू, जेवीएम मैं चल रहा हूं (उबंटू 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





language-agnostic