WeakHashMap ব্যবহার করা সত্ত্বেও আউটঅফমিউরিঅ্যাক্সপশন


9

যদি কল না করা হয় System.gc()তবে সিস্টেমটি একটি আউটআফ মেমরিএক্সসেপশন নিক্ষেপ করবে। আমি কেন 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জিসি তথ্য প্রিন্ট আউট যুক্ত করুন; যেমন আপনি দেখতে পাচ্ছেন, জেভিএম পুরো জিসি রান করার চেষ্টা করে, তবে ব্যর্থ হয়; আমি এখনও কারণ জানি না। এটি খুব আশ্চর্যের বিষয় যে আমি যদি 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

কি জেডি কে সংস্করণ? আপনি কি কোনও XX এবং -Xmx পরামিতি ব্যবহার করেন? আপনি কোন পদক্ষেপে ওওএম পেয়েছেন?
ভ্লাদিস্লাভ কিসলি

1
আমি আমার সিস্টেমে এটি পুনরুত্পাদন করতে পারি না। ডিবাগ মোডে আমি দেখতে পাচ্ছি যে জিসি এটি কাজ করছে। মানচিত্রটি সাফ করা থাকলে আপনি কি ডিবাগ মোডে চেক করতে পারেন?
ম্যাজিকম্যান

jre 1.8.0_212-b10 -Xmx200m আপনি জিসি লগ থেকে আরও বিশদ দেখতে পাবেন যা আমি সংযুক্ত করেছি; thx
ডমিনিক পেং

উত্তর:


7

জেভিএম নিজেই জিসিকে ফোন করবে, তবে এক্ষেত্রে এটি খুব দেরিতে হবে। এক্ষেত্রে স্মৃতি পরিষ্কার করার ক্ষেত্রে দায়িত্বরত কেবল জিসিই নন। মানচিত্রের মানগুলি দৃ strongly়রূপে পৌঁছনীয় এবং মানচিত্র দ্বারা নিজেই সাফ হয়ে যায় যখন এতে নির্দিষ্ট কিছু ক্রিয়াকলাপ ডাকা হয়।

আপনি যদি জিসি ইভেন্টগুলি চালু করেন তবে এখানে আউটপুট দেওয়া হবে (এক্সএক্স: + প্রিন্টজিসি):

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

মানচিত্রে মান রাখার শেষ প্রয়াস হওয়া পর্যন্ত জিসি ট্রিগার করা হয়নি।

WeakHashMap কোনও রেফারেন্স কাতারে মানচিত্র কী না হওয়া পর্যন্ত বাসি প্রবেশগুলি সাফ করতে পারে না। এবং মানচিত্র কীগুলি আবর্জনা সংগ্রহ না করা অবধি কোনও রেফারেন্স কাতারে উপস্থিত হয় না। নতুন মানচিত্রের মানটির জন্য মেমরি বরাদ্দ ট্রিগার করা হয় মানচিত্রে নিজেকে পরিষ্কার করার আগে কোনও সুযোগ নেই। যখন মেমরি বরাদ্দ ব্যর্থ হয় এবং জিসিকে ট্রিগার করে, মানচিত্র কীগুলি সংগ্রহ করা হয়। তবে এটি খুব সামান্য দেরি হয়েছে - নতুন মানচিত্রের মান বরাদ্দ করার জন্য পর্যাপ্ত মেমরিটি মুক্তি দেওয়া হয়নি। আপনি যদি পেডলোড হ্রাস করেন তবে আপনি সম্ভবত নতুন মানচিত্রের মান বরাদ্দ করার জন্য পর্যাপ্ত মেমরি দিয়ে শেষ করবেন এবং বাসি প্রবেশগুলি সরানো হবে।

আর একটি সমাধান হ'ল WeakReferences এ মানগুলি মোড়ানো হতে পারে। এটি জিসি স্বচ্ছ সংস্থানগুলিকে ম্যাপের নিজস্ব হিসাবে এটি করার জন্য অপেক্ষা না করে মঞ্জুরি দেবে। এখানে ফলাফল:

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 থেকে স্থান ছেড়ে দেওয়া দরকার হয়, কমপক্ষে দুবার জিসি করা উচিত; হিমশীতল সময়টি মানচিত্র থেকে চাবি সংগ্রহ করা এবং এগুলি রেফারেন্স সারিতে যুক্ত করা; দ্বিতীয়বার মান সংগ্রহ করতে হয়? তবে আপনি যে ফ্রিস্ট লগ সরবরাহ করেছেন তা থেকে, জেভিএম ইতিমধ্যে দু'বার পূর্ণ জিসি নিয়েছে;
ডমিনিক পেং

আপনি বলছেন, "মানচিত্রের মানগুলি দৃ strongly়রূপে পৌঁছানো যায় এবং যখন নির্দিষ্ট ক্রিয়াকলাপগুলি চালু করা হয় তখন নিজেই মানচিত্রে সাফ হয়ে যায়।" এগুলি কোথা থেকে পৌঁছতে পারে?
অ্যান্ড্রোনিকাস

1
আপনার ক্ষেত্রে কেবল দুটি জিসি রান করা যথেষ্ট হবে না। প্রথমে আপনার একটি জিসি রান দরকার, এটি সঠিক। তবে পরবর্তী পদক্ষেপের জন্য মানচিত্রের সাথেই কিছু ইন্টারঅ্যাকশন প্রয়োজন। আপনার কী দেখতে হবে সেই পদ্ধতিটি java.util.WeakHashMap.expungeStaleEntriesযা রেফারেন্স সারিটি পড়ে এবং মানচিত্র থেকে প্রবেশাগুলি সরিয়ে দেয় ফলে মানগুলি অ্যাক্সেসযোগ্য এবং সংগ্রহের সাপেক্ষে তৈরি হয়। এরপরেই জিসির দ্বিতীয় পাসটি কিছু স্মৃতি মুক্ত করবে। expungeStaleEntriesগেট / পুট / সাইজ বা আপনি সাধারণত কোনও মানচিত্র দিয়ে যা কিছু করেন তার মতো বেশ কয়েকটি ক্ষেত্রে ডাকা হয়। এটাই ধরা।
তাঁবু

1
@ অ্যান্ড্রোনিকাস, এটি এখন পর্যন্ত উইকহ্যাশম্যাপের সবচেয়ে বিভ্রান্তিকর অংশ। এটি একাধিকবার আচ্ছাদিত ছিল। 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। এই সমবর্তী চক্রের অংশ হিসাবে এটি একটি তরুণ জিসি চক্র করে (এটি চলাকালীন আপনার অ্যাপ্লিকেশনটি থামিয়ে দেয় )

 [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
জাভা 8 ডিফল্টভাবে জি 1 জিসি ব্যবহার করে না। এবং ওপির জিসি লগগুলিও পরিষ্কারভাবে দেখায় যে এটি পুরানো প্রজন্মের জন্য সমান্তরাল জিসি ব্যবহার করছে। এবং এই জাতীয় অবিচ্ছিন্ন সংগ্রাহকের জন্য, এই উত্তরে
হোলার

@ হোলজার আমি আজ সকালে এই উত্তরটি পর্যালোচনা করছিলাম শুধুমাত্র বুঝতে পেরে যে এটি সত্যই ParalleGC, আমি আমাকে ভুল প্রমাণ করার জন্য সম্পাদনা করেছি এবং দুঃখিত (এবং আপনাকে ধন্যবাদ)।
ইউজিন

1
"Humongous বরাদ্দ" এখনও একটি সঠিক ইঙ্গিত। অবিচ্ছিন্ন সংগ্রাহক সহ, এটি সূচিত করে যে পুরানো প্রজন্ম পূর্ণ হলে প্রথম জিসি চলবে, সুতরাং পর্যাপ্ত স্থানটি দাবি করতে ব্যর্থতা মারাত্মক করে তুলবে। বিপরীতে, আপনি যখন অ্যারের আকার হ্রাস করবেন তখন পুরানো প্রজন্মের বাকী স্মৃতি বাকী থাকার পরেও একটি অল্প বয়স্ক জিসি ট্রিগার করা হবে, তাই সংগ্রাহক অবজেক্টগুলি প্রচার করতে এবং চালিয়ে যেতে পারেন। অন্যদিকে সমকালীন সংগ্রাহকের জন্য, গাদা শেষ হওয়ার আগেই জিসি ট্রিগার করা স্বাভাবিক, তাই -XX:+UseG1GCএটি জাভা 8 এ কাজ করুন, ঠিক যেমন -XX:+UseParallelOldGCএটি নতুন জেভিএমগুলিতে ব্যর্থ হয়।
হলগার
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.