لماذا أحصل على OutofMemoryError عند إدخال 50000 كائن في hashmap؟

StackOverflow https://stackoverflow.com/questions/235047

  •  04-07-2019
  •  | 
  •  

سؤال

أحاول إدراج حوالي 50000 كائن (وبالتالي 50000 مفتاح) في أ java.util.HashMap<java.awt.Point, Segment>. ومع ذلك ، ما زلت أحصل على استثناء OutofMemory. ((Segment هل صفي الخاص - وزن خفيف جدًا - واحد String الحقل ، و 3 int مجالات).

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.HashMap.resize(HashMap.java:508)
    at java.util.HashMap.addEntry(HashMap.java:799)
    at java.util.HashMap.put(HashMap.java:431)
    at bus.tools.UpdateMap.putSegment(UpdateMap.java:168)

يبدو هذا سخيفًا جدًا لأنني أرى أن هناك الكثير من الذاكرة المتوفرة على الجهاز - سواء في ذاكرة الوصول العشوائي المجانية و HD للذاكرة الافتراضية.

هل من الممكن تشغيل جافا مع بعض متطلبات الذاكرة الصارمة؟ هل يمكنني زيادة هذه؟

هل هناك بعض القيود الغريبة مع HashMap؟ هل سأضطر إلى تنفيذ بلدي؟ هل هناك فصول أخرى تستحق النظر إليها؟

(أقوم بتشغيل Java 5 تحت OS X 10.5 على جهاز Intel مع ذاكرة الوصول العشوائي 2GB.)

هل كانت مفيدة؟

المحلول

يمكنك زيادة الحد الأقصى لحجم الكومة عن طريق المرور -xmx128m (حيث 128 هو عدد ميغابايت) إلى جافا. لا أستطيع أن أتذكر الحجم الافتراضي ، لكن من المذهل أنه كان شيئًا صغيرًا إلى حد ما.

يمكنك التحقق برنامجيا كمية الذاكرة المتاحة باستخدام مدة العرض صف دراسي.

// Get current size of heap in bytes
long heapSize = Runtime.getRuntime().totalMemory();

// Get maximum size of heap in bytes. The heap cannot grow beyond this size.
// Any attempt will result in an OutOfMemoryException.
long heapMaxSize = Runtime.getRuntime().maxMemory();

// Get amount of free memory within the heap in bytes. This size will increase
// after garbage collection and decrease as new objects are created.
long heapFreeSize = Runtime.getRuntime().freeMemory();

(مثال من مطورو جافا تقويم)

هذا هو أيضا معالجة جزئيا في كثيرا ما يتم طرح الأسئلة حول نقطة الساخنة Java VM, ، وفي صفحة ضبط Java 6 GC.

نصائح أخرى

يقترح بعض الأشخاص تغيير معلمات hashmap لتشديد متطلبات الذاكرة. أود أن أقترح قياس بدلا من التخمين; ؛ قد يكون شيئًا آخر يسبب OOM. على وجه الخصوص ، أقترح استخدام أي منهما Netbeans profiler أو VisualVM (الذي يأتي مع Java 6 ، لكني أرى أنك عالق مع Java 5).

هناك شيء آخر يجب تجربته إذا كنت تعرف عدد الكائنات مسبقًا هو استخدام مُنشئ HashMap (سعة int ، تحميل مزدوج) بدلاً من واحد من عدم الحدوث الافتراضي الذي يستخدم الافتراضيات (16،0.75). إذا تجاوز عدد العناصر الموجودة في hashmap (سعة * loadFactor) ، فسيتم تغيير حجم الصفيف الأساسي في hashmap إلى القوة التالية 2 وسيتم إعادة صياغة الجدول. تتطلب هذه الصفيف أيضًا منطقة متداولة من الذاكرة ، لذا على سبيل المثال ، إذا كنت تتضاعف من 32768 إلى صفيف بحجم 65536 ، فستحتاج إلى قطعة من الذاكرة بحجم 256 كيلو بايت. لتجنب التخصيص الإضافي وإعادة صياغة العقوبات ، ما عليك سوى استخدام جدول تجزئة أكبر من البداية. سوف يقلل أيضًا من فرصة ألا يكون لديك مساحة متداخلة من الذاكرة كبيرة بما يكفي لتناسب الخريطة.

يتم دعم التطبيقات بواسطة المصفوفات عادة. المصفوفات عبارة عن كتل ذات حجم ثابت من الذاكرة. يبدأ تطبيق HashMap بتخزين البيانات في أحد هذه المصفوفات بسعة معينة ، على سبيل المثال 100 كائن.

إذا كانت تملأ الصفيف واستمرت في إضافة كائنات تحتاج الخريطة إلى زيادة حجم الصفيف سراً. نظرًا لأن المصفوفات ثابتة ، فإنها تقوم بذلك عن طريق إنشاء صفيف جديد تمامًا ، في الذاكرة ، إلى جانب الصفيف الحالي ، أكبر قليلاً. يشار إلى هذا باسم تنمية الصفيف. ثم يتم نسخ جميع العناصر من الصفيف القديم إلى الصفيف الجديد ويتم تصنيف المصفوفة القديمة على أمل جمع القمامة وتحرير الذاكرة في مرحلة ما.

عادةً ما يكون الرمز الذي يزيد من قدرة الخريطة عن طريق نسخ العناصر إلى صفيف أكبر هو سبب هذه المشكلة. هناك تطبيقات "غبية" وتلك الذكية التي تستخدم عامل نمو أو تحميل يحدد حجم الصفيف الجديد بناءً على حجم الصفيف القديم. تخفي بعض التطبيقات هذه المعلمات والبعض الآخر لا يمكنك دائمًا تعيينها. المشكلة هي أنه عندما لا يمكنك ضبطه ، فإنه يختار بعض عامل التحميل الافتراضي ، مثل 2. وبالتالي فإن الصفيف الجديد هو ضعف حجم القديم. الآن ، من المفترض أن تكون خريطة 50K الخاصة بك لديها مجموعة دعم من 100 ألف.

ابحث لمعرفة ما إذا كان يمكنك تقليل عامل التحميل إلى 0.25 أو شيء من هذا القبيل. هذا يسبب المزيد من تصادمات خريطة التجزئة التي تؤلمني الأداء ولكنك تضرب عنق الزجاجة الذاكرة وتحتاج إلى القيام بذلك.

استخدم هذا المنشئ:

(http://java.sun.com/javase/6/docs/api/java/util/hashmap.html#hashmap(int, ، يطفو))

ربما تحتاج إلى تعيين العلم -xmx512m أو بعض الأرقام الأكبر عند بدء تشغيل Java. أعتقد أن 64 ميغابايت هو الافتراضي.

تم تحريره لإضافة: بعد معرفة مقدار الذاكرة التي تستخدمها كائناتك بالفعل مع Profiler ، قد ترغب في النظر في مراجع ضعيفة أو مراجع ناعمة للتأكد لم تعد تستخدمها.

قد ترغب أيضًا في إلقاء نظرة على هذا:

http://java.sun.com/docs/hotspot/gc/

ضمنيًا في هذه الإجابات ، فإن Java لديها حجم ثابت للذاكرة ولا تنمو إلى ما وراء حجم الكومة المكون. هذا على عكس ، على سبيل المثال ، C ، حيث يتم تقييده فقط بواسطة الجهاز الذي يتم تشغيله عليه.

بشكل افتراضي ، يستخدم JVM مساحة كومة محدودة. الحد الأقصى يعتمد على تطبيق JVM ، وليس من الواضح ما الذي تستخدمه JVM. على OS بخلاف Windows ، ستستخدم Sun JVM 32 بت على جهاز مع 2 غيغابايت أو أكثر حجمًا أقصى افتراضيًا يبلغ 1/4 من الذاكرة الفعلية ، أو 512 ميغابايت في حالتك. ومع ذلك ، فإن الافتراضي لوضع "العميل" JVM هو فقط 64 ميجابايت بحجم الكومة الحد الأقصى فقط ، وهو ما قد يكون ما تواجهه. يجوز لـ JVM للبائع الآخر تحديد الإعدادات الافتراضية المختلفة.

بالطبع ، يمكنك تحديد حد الكومة بشكل صريح مع -Xmx<NN>m خيار ل java, ، أين <NN> هو عدد megabytes للكومة.

كتخمين تقريبي ، يجب أن يستخدم جدول التجزئة الخاص بك فقط حوالي 16 ميجابايت ، لذلك يجب أن يكون هناك بعض الأشياء الكبيرة الأخرى على الكومة. إذا كنت تستطيع استخدام أ Comparable المفتاح في TreeMap, من شأنه أن ينقذ بعض الذاكرة.

نرى "بيئة العمل في 5.0 JVM" لمزيد من التفاصيل.

The Java heap space is limited by default, but that still sounds extreme (though how big are your 50000 segments?)

I am suspecting that you have some other problem, like the arrays in the set growing too big because everything gets assigned into the same "slot" (also affects performance, of course). However, that seems unlikely if your points are uniformly distributed.

I'm wondering though why you're using a HashMap rather than a TreeMap? Even though points are two dimensional, you could subclass them with a compare function and then do log(n) lookups.

Random thought: The hash buckets associated with HashMap are not particularly memory efficient. You may want to try out TreeMap as an alternative and see if it still provide sufficient performance.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top