Почему я получаю OutOfMemoryError при вставке 50 000 объектов в HashMap?

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

  •  04-07-2019
  •  | 
  •  

Вопрос

Я пытаюсь вставить около 50 000 объектов (и, следовательно, 50 000 ключей) в 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-пространстве для виртуальной памяти.

Возможно ли, что Java работает с некоторыми строгими требованиями к памяти? Могу ли я увеличить это?

Есть ли какие-то странные ограничения в HashMap? Я собираюсь реализовать свое собственное? Есть ли другие классы, на которые стоит обратить внимание?

(Я использую Java 5 под OS X 10.5 на машине Intel с 2 ГБ ОЗУ.)

Это было полезно?

Решение

Вы можете увеличить максимальный размер кучи, передав -Xmx128m (где 128 - количество мегабайт) в java. Я не могу вспомнить размер по умолчанию, но мне кажется, что это было что-то довольно маленькое.

Вы можете программно проверить, сколько памяти доступно, используя Класс выполнения .

// 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 )

Это также частично рассматривается в Часто задаваемые вопросы о виртуальной машине Java HotSpot и на странице настройки Java 6 GC .

Другие советы

Некоторые люди предлагают изменить параметры HashMap, чтобы ужесточить требования к памяти. Я бы предложил измерить, а не угадать ; это может быть что-то еще, что вызывает OOME. В частности, я бы предложил использовать Профилировщик NetBeans или VisualVM (который поставляется с Java 6, но я вижу, что вы застряли с Java 5).

Еще одна вещь, которую стоит попробовать, если вы заранее знаете количество объектов, - это использовать конструктор HashMap (intacity, double loadfactor) вместо стандартного no-arg, который использует значения по умолчанию (16,0.75). Если число элементов в вашей HashMap превышает (емкость * loadfactor), базовый массив в HashMap будет изменен до следующей степени 2, и таблица будет перефразирована. Для этого массива также требуется непрерывная область памяти, поэтому, например, если вы удвоите размер массива от 32768 до 65536, вам потребуется кусок свободной памяти размером 256 КБ. Чтобы избежать дополнительного распределения и перефразирования штрафов, просто используйте большую хэш-таблицу с самого начала. Это также уменьшит вероятность того, что у вас не будет смежной области памяти, достаточно большой, чтобы уместиться на карте.

Реализации обычно поддерживаются массивами. Массивы - это блоки памяти фиксированного размера. Реализация хэш-карты начинается с хранения данных в одном из этих массивов с заданной емкостью, скажем, 100 объектов.

Если он заполняет массив и вы продолжаете добавлять объекты, карта должна тайно увеличить размер массива. Поскольку массивы фиксированы, он делает это путем создания совершенно нового массива в памяти вместе с текущим массивом, который немного больше. Это называется ростом массива. Затем все элементы из старого массива копируются в новый массив, и на старый массив разыменовывается надежда на то, что он будет собирать мусор и освобождать память в какой-то момент.

Обычно причиной такой проблемы является код, который увеличивает емкость карты путем копирования элементов в больший массив. Есть & Quot; dumb & Quot; реализации и умные, которые используют коэффициент роста или загрузки, который определяет размер нового массива в зависимости от размера старого массива. Некоторые реализации скрывают эти параметры, а некоторые нет, поэтому вы не всегда можете их установить. Проблема в том, что когда вы не можете установить его, он выбирает некоторый коэффициент загрузки по умолчанию, например 2. Таким образом, новый массив в два раза больше старого. Теперь ваша предположительно карта 50 КБ имеет резервный массив 100 КБ.

Посмотрите, сможете ли вы снизить коэффициент загрузки до 0,25 или около того. это вызывает больше коллизий хеш-карт, что снижает производительность, но вы сталкиваетесь с узким местом в памяти и должны это делать.

Используйте этот конструктор:

( http: // java.sun.com/javase/6/docs/api/java/util/HashMap.html#HashMap(int , float))

Возможно, вам нужно установить флаг -Xmx512m или большее число при запуске java. Я думаю, что по умолчанию 64 МБ.

Отредактировано, чтобы добавить: После того, как вы выясните, сколько памяти ваши объекты фактически используют с профилировщиком, вы, возможно, захотите просмотреть слабые или мягкие ссылки, чтобы убедиться, что вы случайно не удерживаете часть заложенной памяти из сборщика мусора, когда вас нет. дольше их использовать.

Также, возможно, захотите взглянуть на это:

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

В этих ответах подразумевается, что Java имеет фиксированный размер для памяти и не превышает установленного максимального размера кучи. Это не похоже, скажем, на C, где он ограничен только машиной, на которой он запущен.

По умолчанию JVM использует ограниченное пространство кучи. Ограничение зависит от реализации JVM, и неясно, какую JVM вы используете. В ОС, отличной от Windows, 32-битная Sun JVM на машине с 2 ГБ или более будет использовать максимальный размер кучи по умолчанию, равный 1/4 физической памяти, или 512 МБ в вашем случае. Тем не менее, по умолчанию для & Quot; client & Quot; В режиме JVM максимальный размер кучи составляет всего 64 Мб, что может быть тем, с чем вы столкнулись. JVM других поставщиков могут выбирать другие значения по умолчанию.

Конечно, вы можете явно указать ограничение кучи с помощью опции -Xmx<NN>m java, где <NN> - это количество мегабайт для кучи.

По грубым предположениям, ваша хеш-таблица должна использовать только около 16 Мб, поэтому в куче должны быть другие крупные объекты. Если бы вы могли использовать клавишу Comparable в TreeMap, это сэкономило бы немного памяти.

См. " эргономика в 5.0 JVM " для получения более подробной информации.

Пространство кучи Java по умолчанию ограничено, но это все еще звучит экстремально (хотя насколько велики ваши 50000 сегментов?)

Я подозреваю, что у вас есть другая проблема, например, массивы в наборе становятся слишком большими, потому что все попадает в один и тот же " slot " (также влияет на производительность, конечно). Однако это маловероятно, если ваши очки распределены равномерно.

Мне интересно, почему вы используете HashMap, а не TreeMap? Несмотря на то, что точки являются двухмерными, вы можете разделить их на подклассы с помощью функции сравнения, а затем выполнить поиск в log (n).

Случайное размышление. Хэш-блоки, связанные с HashMap, не особенно эффективны для памяти. Возможно, вы захотите попробовать TreeMap в качестве альтернативы и посмотреть, обеспечивает ли он достаточную производительность.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top