Почему я получаю OutOfMemoryError при вставке 50 000 объектов в HashMap? - PullRequest
13 голосов
/ 24 октября 2008

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

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

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

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

Ответы [ 10 ]

21 голосов
/ 24 октября 2008

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

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

// 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 VM и на странице настройки Java 6 GC .

7 голосов
/ 25 октября 2008

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

3 голосов
/ 25 октября 2008

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

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

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

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

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

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

3 голосов
/ 25 октября 2008

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

2 голосов
/ 25 октября 2008

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

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

1 голос
/ 26 октября 2008

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

1 голос
/ 25 октября 2008

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

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

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

1 голос
/ 25 октября 2008

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

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

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

Подробнее см. "Эргономика в 5.0 JVM" .

1 голос
/ 25 октября 2008

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

1 голос
/ 24 октября 2008

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

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

...