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

Я пытаюсь вставить около 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 ГБ ОЗУ.)

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
14
0
21 664
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

Ответ принят как подходящий

Вы можете увеличить максимальный размер кучи, передав -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.

Как определить текущий размер, чтобы знать будущее? Спасибо!

Frank Krueger 24.10.2008 23:57

Хотя очень странно, что у вас будет так мало доступной памяти, что вы не сможете добавить 50000 небольших объектов в хеш. Не так уж и много.

Allain Lalonde 24.10.2008 23:59

Спасибо! Накачал его до 2048 МБ, и моя программа наконец-то завершила выполнение! Ха-ха. Вау.

Frank Krueger 25.10.2008 00:05

Придется согласиться с Алленом - 2048 Мбайт многовато. Возможно, вы захотите использовать профилировщик, чтобы увидеть, откуда берутся все эти выделения.

Michael Myers 25.10.2008 00:11

Размер клиентской виртуальной машины по умолчанию составляет 64 м.

Brandon DuRette 25.10.2008 00:17

@Brandon: это начальный размер или максимальный?

Michael Myers 25.10.2008 00:22

в Windows 2048 даже не позволит вам запустить виртуальную машину. Максимальный размер 32-битных окон составляет около 1,4 ГБ, в зависимости от того, какие другие DLL загружены. в OSX, как говорится на оригинальном плакате, виртуальная машина может запуститься или не запуститься, если вы попробуете максимальную память в качестве параметра MX.

John Gardner 25.10.2008 00:48

Конечно, я мог бы использовать профилировщик и поработать над функциями хеширования, чтобы уменьшить использование памяти, но этот инструмент запускается один или два раза в месяц. Мое время лучше потратить на оптимизацию продукта, а не на инструмент поддержки. Но спасибо за предложения!

Frank Krueger 25.10.2008 05:25

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

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

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

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

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

@Frank Krueger: Этот выбор был сделан для реализации более эффективного сборщика мусора. Фиксированный максимальный размер помогает оптимизировать эту вещь.

Mnementh 25.10.2008 11:44

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

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

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

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

Повышение предела сработало, но большое спасибо за ссылку на TreeMap.

Frank Krueger 25.10.2008 00:35

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

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

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

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

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

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

(http://java.sun.com/javase/6/docs/api/java/util/HashMap.html#HashMap(int, с плавающей запятой))

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

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

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

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

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

Интересно, не могли бы вы подробнее рассказать об этом Кевине?

James McMahon 21.11.2008 01:40

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