WeakHashMap का उपयोग करने के बावजूद OutOfMemoryException


9

यदि कॉल नहीं करते हैं System.gc(), तो सिस्टम एक OutOfMemoryException को फेंक देगा। मुझे नहीं पता कि मुझे System.gc()स्पष्ट रूप से कॉल करने की आवश्यकता क्यों है ; जेवीएम को gc()खुद को सही कहना चाहिए ? कृपया सलाह दें।

निम्नलिखित मेरा परीक्षण कोड है:

public static void main(String[] args) throws InterruptedException {
    WeakHashMap<String, int[]> hm = new WeakHashMap<>();
    int i  = 0;
    while(true) {
        Thread.sleep(1000);
        i++;
        String key = new String(new Integer(i).toString());
        System.out.println(String.format("add new element %d", i));
        hm.put(key, new int[1024 * 10000]);
        key = null;
        //System.gc();
    }
}

निम्नलिखित के रूप में, -XX:+PrintGCDetailsजीसी जानकारी को प्रिंट करने के लिए जोड़ें ; जैसा कि आप देख रहे हैं, वास्तव में, JVM एक पूर्ण GC रन करने की कोशिश करता है, लेकिन विफल रहता है; मुझे अभी भी इसका कारण नहीं पता है। यह बहुत अजीब है कि अगर मैं असहज हूंSystem.gc(); लाइन को , तो परिणाम सकारात्मक है:

add new element 1
add new element 2
add new element 3
add new element 4
add new element 5
[GC (Allocation Failure) --[PSYoungGen: 48344K->48344K(59904K)] 168344K->168352K(196608K), 0.0090913 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 48344K->41377K(59904K)] [ParOldGen: 120008K->120002K(136704K)] 168352K->161380K(196608K), [Metaspace: 5382K->5382K(1056768K)], 0.0380767 secs] [Times: user=0.09 sys=0.03, real=0.04 secs] 
[GC (Allocation Failure) --[PSYoungGen: 41377K->41377K(59904K)] 161380K->161380K(196608K), 0.0040596 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 41377K->41314K(59904K)] [ParOldGen: 120002K->120002K(136704K)] 161380K->161317K(196608K), [Metaspace: 5382K->5378K(1056768K)], 0.0118884 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at test.DeadLock.main(DeadLock.java:23)
Heap
 PSYoungGen      total 59904K, used 42866K [0x00000000fbd80000, 0x0000000100000000, 0x0000000100000000)
  eden space 51712K, 82% used [0x00000000fbd80000,0x00000000fe75c870,0x00000000ff000000)
  from space 8192K, 0% used [0x00000000ff800000,0x00000000ff800000,0x0000000100000000)
  to   space 8192K, 0% used [0x00000000ff000000,0x00000000ff000000,0x00000000ff800000)
 ParOldGen       total 136704K, used 120002K [0x00000000f3800000, 0x00000000fbd80000, 0x00000000fbd80000)
  object space 136704K, 87% used [0x00000000f3800000,0x00000000fad30b90,0x00000000fbd80000)
 Metaspace       used 5409K, capacity 5590K, committed 5760K, reserved 1056768K
  class space    used 576K, capacity 626K, committed 640K, reserved 1048576K

क्या jdk संस्करण? क्या आप किसी भी -Xms और -Xmx मापदंडों का उपयोग करते हैं? ओओएम आपको किस कदम पर मिला?
व्लादिस्लाव Kysliy

1
मैं अपने सिस्टम पर इसे पुन: पेश नहीं कर सकता। डिबग मोड में मैं देख सकता हूं कि GC यह काम कर रहा है। क्या आप डिबग मोड में जांच सकते हैं कि क्या मानचित्र वास्तव में साफ हो रहा है या नहीं?
मैजिकम

jre 1.8.0_212-b10 -Xmx200m आप मेरे द्वारा संलग्न gc लॉग से अधिक विवरण देख सकते हैं; thx
डोमिनिक पेंग

जवाबों:


7

जेवीएम अपने आप में जीसी को बुलाएगा, लेकिन इस मामले में बहुत देर हो जाएगी। यह केवल जीसी नहीं है जो इस मामले में स्मृति को साफ करने के लिए जिम्मेदार है। मानचित्र मान दृढ़ता से पहुंच योग्य होते हैं और मानचित्र द्वारा ही साफ कर दिए जाते हैं, जब उस पर कुछ कार्य किए जाते हैं।

यदि आप GC घटनाओं (XX: + PrintGC) को चालू करते हैं, तो यहां आउटपुट है:

add new element 1
add new element 2
add new element 3
add new element 4
add new element 5
add new element 6
add new element 7
[GC (Allocation Failure)  2407753K->2400920K(2801664K), 0.0123285 secs]
[GC (Allocation Failure)  2400920K->2400856K(2801664K), 0.0090720 secs]
[Full GC (Allocation Failure)  2400856K->2400805K(2590720K), 0.0302800 secs]
[GC (Allocation Failure)  2400805K->2400805K(2801664K), 0.0069942 secs]
[Full GC (Allocation Failure)  2400805K->2400753K(2620928K), 0.0146932 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

मानचित्र में मान डालने के अंतिम प्रयास तक GC को ट्रिगर नहीं किया गया है।

WeakHashMap तब तक बासी प्रविष्टियों को साफ़ नहीं कर सकता, जब तक कि संदर्भ की कतार पर मैप कीज़ न हों। और जब तक वे कचरा एकत्र नहीं करते, तब तक नक्शे की चाबियाँ संदर्भ कतार में नहीं होती हैं। नए मानचित्र मान के लिए मेमोरी आवंटन को ट्रिगर किया जाता है, इससे पहले कि नक्शा खुद को खाली करने का कोई मौका हो। जब मेमोरी आवंटन विफल हो जाता है और GC को ट्रिगर करता है, तो मैप कीज़ एकत्रित हो जाती हैं। लेकिन यह बहुत कम देर हो चुकी है - पर्याप्त मेमोरी को नया मानचित्र मान आवंटित करने के लिए मुक्त नहीं किया गया है। यदि आप पेलोड को कम करते हैं, तो आप संभवतः नए मैप वैल्यू को आवंटित करने के लिए पर्याप्त मेमोरी के साथ समाप्त हो जाते हैं और बासी प्रविष्टियों को हटा दिया जाएगा।

एक और समाधान WeakReference में मूल्यों को स्वयं लपेट सकता है। यह GC स्पष्ट संसाधनों को बिना मानचित्र के प्रतीक्षा किए अनुमति देगा कि वह इसे स्वयं कर सके। यहाँ उत्पादन है:

add new element 1
add new element 2
add new element 3
add new element 4
add new element 5
add new element 6
add new element 7
[GC (Allocation Failure)  2407753K->2400920K(2801664K), 0.0133492 secs]
[GC (Allocation Failure)  2400920K->2400888K(2801664K), 0.0090964 secs]
[Full GC (Allocation Failure)  2400888K->806K(190976K), 0.1053405 secs]
add new element 8
add new element 9
add new element 10
add new element 11
add new element 12
add new element 13
[GC (Allocation Failure)  2402096K->2400902K(2801664K), 0.0108237 secs]
[GC (Allocation Failure)  2400902K->2400838K(2865664K), 0.0058837 secs]
[Full GC (Allocation Failure)  2400838K->1024K(255488K), 0.0863236 secs]
add new element 14
add new element 15
...
(and counting)

काफी बेहतर।


अपने उत्तर के लिए Thx, लगता है कि आपका निष्कर्ष सही है; जबकि मैं 1024 * 10000 से 1024 * 1000 तक पेलोड को कम करने की कोशिश करता हूं; कोड ठीक काम कर सकता है; लेकिन मैं अपने स्पष्टीकरण को बहुत कम नहीं समझूंगा; आपके अर्थ के रूप में, यदि आपको WeakHashMap से स्थान जारी करने की आवश्यकता है, तो कम से कम दो बार जीसी करना चाहिए; फ्रिस्ट समय मानचित्र से कुंजियाँ एकत्रित करने और उन्हें संदर्भ कतार में जोड़ने का है; दूसरी बार मान एकत्र करने के लिए है? लेकिन आपके द्वारा प्रदान किए गए फ्रिस्ट लॉग से, वास्तव में, जेवीएम पहले ही दो बार पूर्ण जीसी ले चुका था;
डोमिनिक पेंग

आप कह रहे हैं, "मैप मान दृढ़ता से पहुंच योग्य हैं और नक्शे से ही साफ हो जाते हैं, जब उस पर कुछ कार्रवाई की जाती है।" वे कहां से उपलब्ध हैं?
एंड्रॉनिकस

1
यह आपके मामले में केवल दो GC रन के लिए पर्याप्त नहीं होगा। पहले आपको एक जीसी रन चाहिए, यह सही है। लेकिन अगले चरण में नक्शे के साथ ही कुछ बातचीत की आवश्यकता होगी। आपको java.util.WeakHashMap.expungeStaleEntriesजो दिखना चाहिए वह विधि है जो संदर्भ पंक्ति पढ़ती है और नक्शे से प्रविष्टियों को हटाती है और इस प्रकार मान को अप्राप्य बनाती है और संग्रह के अधीन है। उसके बाद ही जीसी का दूसरा पास होने पर कुछ मेमोरी खाली हो जाएगी। expungeStaleEntriesकई मामलों में कहा जाता है जैसे गेट / पुट / साइज़ या बहुत कुछ जो आप आमतौर पर नक्शे के साथ करते हैं। वह पकड़ है।
तंबू

1
@Andronicus, यह अभी तक WeakHashMap का सबसे भ्रमित करने वाला हिस्सा है। इसे कई बार कवर किया गया था। stackoverflow.com/questions/5511279/…
तमाशा

2
@ और यह उत्तर , विशेष रूप से दूसरी छमाही, साथ ही सहायक हो सकता है। इसके अलावा यह प्रश्नोत्तर
होल्गर

5

अन्य उत्तर वास्तव में सही है, मैंने अपना संपादन किया। एक छोटे से परिशिष्ट के रूप में, G1GCइस व्यवहार को प्रदर्शित नहीं करेगा, इसके विपरीत ParallelGC; जिसके तहत डिफ़ॉल्ट है java-8

आपको क्या लगता है कि अगर मैं आपके कार्यक्रम को थोड़ा बदल दूं ( jdk-8साथ में चलाऊं -Xmx20m)

public static void main(String[] args) throws InterruptedException {
    WeakHashMap<String, int[]> hm = new WeakHashMap<>();
    int i = 0;
    while (true) {
        Thread.sleep(200);
        i++;
        String key = "" + i;
        System.out.println(String.format("add new element %d", i));
        hm.put(key, new int[512 * 1024 * 1]); // <--- allocate 1/2 MB
    }
}

यह ठीक काम करेगा। ऐसा क्यों है? क्योंकि यह आपके प्रोग्राम को नई एंट्रीजेशन के लिए पर्याप्त सांस लेने की जगह देता है, ताकि WeakHashMapउसकी एंट्रीज क्लियर हो सकें । और दूसरा जवाब पहले से ही बताता है कि ऐसा कैसे होता है।

अब, में G1GC, चीजें थोड़ी अलग होंगी। जब इतनी बड़ी वस्तु आवंटित की जाती है ( आमतौर पर 1/2 से अधिक एक एमबी ), तो इसे ए कहा जाएगा humongous allocation। जब ऐसा होता है तो एक समवर्ती जीसी ट्रिगर हो जाएगा। उस चक्र के भाग के रूप में: एक युवा संग्रह को चालू किया जाएगा और एक Cleanup phaseऐसी पहल की जाएगी जो ईवेंट को पोस्ट करने का ध्यान रखेगी ReferenceQueue, ताकि WeakHashMapइसकी प्रविष्टियां साफ़ हो जाएं।

तो इस कोड के लिए:

public static void main(String[] args) throws InterruptedException {
    Map<String, int[]> hm = new WeakHashMap<>();
    int i = 0;
    while (true) {
        Thread.sleep(1000);
        i++;
        String key = "" + i;
        System.out.println(String.format("add new element %d", i));
        hm.put(key, new int[1024 * 1024 * 1]); // <--- 1 MB allocation
    }
}

मैं jdk-13 के साथ चलता हूं (जहां G1GCडिफ़ॉल्ट है)

java -Xmx20m "-Xlog:gc*=debug" gc.WeakHashMapTest

यहाँ लॉग का एक हिस्सा है:

[2.082s][debug][gc,ergo] Request concurrent cycle initiation (requested by GC cause). GC cause: G1 Humongous Allocation

यह पहले से ही कुछ अलग करता है। यह शुरू होता है concurrent cycle( जब आपका आवेदन चल रहा हो), क्योंकि वहाँ एक था G1 Humongous Allocation। इस समवर्ती चक्र के भाग के रूप में यह एक युवा GC चक्र करता है (जो दौड़ते समय आपके एप्लिकेशन को रोकता है )

 [2.082s][info ][gc,start] GC(0) Pause Young (Concurrent Start) (G1 Humongous Allocation)

उस युवा जीसी के हिस्से के रूप में , यह मानवीय क्षेत्रों को भी साफ करता है , यहां दोष है


अब आप देख सकते हैं कि jdk-13पुराने क्षेत्र में कचरे के ढेर के लिए इंतजार नहीं करता है जब वास्तव में बड़ी वस्तुओं को आवंटित किया जाता है, लेकिन एक समवर्ती जीसी चक्र को ट्रिगर करता है , जिसने दिन को बचाया; jdk-8 के विपरीत।

आप क्या DisableExplicitGCऔर / या ExplicitGCInvokesConcurrentमतलब पढ़ना चाहते हैं , के साथ मिलकर System.gcऔर समझ सकते हैं कि कॉलिंग System.gcवास्तव में यहाँ क्यों मदद करता है।


1
Java 8 डिफ़ॉल्ट रूप से G1GC का उपयोग नहीं करता है। और ओपी के जीसी लॉग भी स्पष्ट रूप से दिखाते हैं कि यह पुरानी पीढ़ी के समानांतर जीसी का उपयोग कर रहा है। और इस तरह के एक गैर-समवर्ती कलेक्टर के लिए, यह इस उत्तर
होल्गर

@ होल्गर मैं आज सुबह ही इस जवाब की समीक्षा कर रहा था कि यह महसूस करने के लिए कि यह वास्तव में है ParalleGC, मैंने संपादित किया है और मुझे गलत साबित करने के लिए खेद (और धन्यवाद) दिया है।
यूजीन

1
"विनम्र आवंटन" अभी भी एक सही संकेत है। एक गैर-समवर्ती कलेक्टर के साथ, इसका तात्पर्य है कि पुरानी पीढ़ी के पूर्ण होने पर पहली जीसी चलेगी, इसलिए पर्याप्त स्थान को पुनः प्राप्त करने में विफलता इसे घातक बना देगी। इसके विपरीत, जब आप सरणी आकार को कम करते हैं, तो एक युवा जीसी चालू हो जाएगा जब पुरानी पीढ़ी में अभी भी मेमोरी शेष है, इसलिए कलेक्टर ऑब्जेक्ट्स को बढ़ावा दे सकता है और जारी रख सकता है। एक समवर्ती कलेक्टर के लिए, दूसरी ओर, ढेर समाप्त होने से पहले जीसी को ट्रिगर करना सामान्य है, इसलिए -XX:+UseG1GCइसे जावा 8 में काम करें, जैसे -XX:+UseParallelOldGCयह नए जेवीएम में विफल हो जाता है।
होल्गर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.