Использование памяти байтового массива в Java

Для эвристической предварительно вычисленной таблицы мне нужен массив байтов с 1504935936 элементами. Это займет около 1,5 ГБ памяти.

public class Main{
    public static void main(String[] args){
        byte[] arr = new byte[1504935936];
    }
}

Почему у меня есть "OutOfMemoryError: Java heap space" -Exception, если я даю программе 2 ГБ ОЗУ с помощью

java -Xmx2048M Main

С

java -Xmx2153M Main

он работает. Зачем нужно много оперативной памяти?

Ответ 1

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

Этот массив байтов должен быть выделен как один непрерывный объем памяти объемом 1,5 ГБ в кучном пространстве Java. (Это не требуется спецификацией языка Java, но AFAIK - это то, как все текущие реализации JVM действительно работают.) Некоторые из вашего пространства кучи потребляются и, что более важно, фрагментируются другими распределениями памяти, которые происходят в вашей предыдущей программе для выделения этого большого массива байтов. То, что java -Xmx2153M Main может быть настолько большим, что вы должны сделать общую кучу, чтобы там было непрерывное пространство на 1,5 ГБ, оставленное к тому времени, когда вы дойдете до выделения.

Если вы нарезаете этот большой массив на 100 меньших массивов размером 1/100, он может вписаться в меньшую кучу, потому что он не так чувствителен к фрагментации кучи.

Ответ 2

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

Получите хороший профилировщик памяти (желательно один с визуальным дисплеем) и присоедините его к вашему jvm. То, что вы увидите, это то, что современный jvm не имеет одного большого кучного пространства, но вместо этого будет иметь несколько пулов (также называемых поколениями). Как правило, "старое поколение" является самым большим, но у вас также будет несколько других. Вместе все эти пулы должны содержать примерно до кучи, которое вы разрешили для jvm.

Таким образом, ваш параметр "-Xmx2048M" не приводит к кучке с единственным пулом, который может поддерживать массив размером 1,5 ГБ (как отмечают другие, вам нужен единый непрерывный блок памяти для массива, то есть кусок память, полностью содержащаяся в одном пуле/поколении).

Ответ 3

Если процесс выполняется как 32-разрядный процесс, большинство ОС сохраняют только около 2 ГБ адресного пространства для процесса, остальные 2 ГБ адресного пространства сопоставляются для содержимого ядра (так что когда ваш процесс вызывает ядро, вы не нужно выполнять столько переключателей контекста).

Даже если ваш компьютер имеет 8 ГБ оперативной памяти или 2 ГБ с 2 ГБ свопа, каждый 32-разрядный процесс будет иметь возможность выделять и адресовать 2 ГБ, если только вы не используете PAE или что-то подобное.

Это вызывает несколько проблем. Во-первых, вам может не хватить необработанного адресного пространства для хранения общего размера всех распределений. Во-вторых, у вас может не быть одного непрерывного фрагмента памяти, который является размером массива, который вам нужен. Java и несколько других сред VM используют отдельные кучи для хранения различных типов памяти, например, кучу больших объектов, отличную от gen 0, или gen 1 и т.д. Каждый раздел приводит к меньшим смежным областям.

В 64-битном процессе ограничения адресного пространства почти исчезли, однако вы все равно не можете иметь достаточную непрерывную, подлежащую передаче, разрешенную java-память для удовлетворения запроса. Если вы установите Java только для обеспечения всего 2 ГБ памяти, у вас могут возникнуть проблемы с поиском достаточно непрерывной памяти для удовлетворения запроса.

Имейте в виду, что процессу требуется значительная часть памяти для хранения кодовых страниц для вашей программы и требуется память для среды java. Это может быть пара сотен мегабайт памяти, в зависимости от требований остальной части вашей программы.

Может быть инструктировано выполнить вашу простую программу, в то время как она выделяет 1-элементный массив байтов и проверяет память с помощью SysInternal VMMap, чтобы получить идея о том, откуда происходят ваши издержки памяти, за исключением вашего большого выделения.

Затем сделайте снимок с вашим большим распределением и посмотрите, что вы получаете.

Ответ 4

jmap и jhat являются хорошими командами для обнаружения того, кто использует какие части памяти. Я рекомендую начать с кучи кучи и посмотреть на них. Только часть доступной памяти выделяется куче в Java. Существует также память, необходимая для запуска виртуальной машины и пространства стека. Куча также разделена на части. OutOfMemoryException задается при заполнении одной части (поколение). Инструменты анализатора кучи помогут вам определить, что именно происходит.

Для чего-то более быстрого, вы также можете попробовать проверить эти значения перед распределением массива:

Runtime.getRuntime().totalMemory();
Runtime.getRuntime().freeMemory();

Вот несколько полезных ссылок для получения дополнительной информации об использовании памяти:

Ответ 5

Объем памяти JVM разделен на несколько областей.

Используя опцию -Xmx, вы задаете кучу java размера, которая для HotSpot построена с четырьмя пробелами, Eden, Survivor 1 и 2 и занята.

Помните, что первое дерево относится к молодому пространству, а отдых называется старым.

По умолчанию молодое пространство потребляет 1/3 значения -Xmx.

Тогда означает, когда вы объявляете -Xmx 2g. Это молодое пространство будет потреблять более 600 м.

С такими большими данными вы можете использовать Direct ByteBuffer, описанный здесь Петр:

IntBuffer arr = ByteBuffer.allocateDirect(size)
                            .order(ByteOrder.nativeOrder()).asIntBuffer(); 
 arr.put(n, 1);// arr[n] = 1
 arr.get(n);   // arr[n]

Java - куча против прямого доступа к памяти


Чтобы диагностировать, как куча java используется вашим приложением в HotSpot Oracle VM, вы можете найти инструмент, поставляемый с SDK под названием jstat. Этот инструмент дает вам быструю обратную связь о том, что происходит с вашим приложением.

В вашем случае наиболее интересным вариантом для вас будет gccapacity, который предоставляет данные о Генерации пула памяти и пропускной способности пространства и gcutil с Сводкой статистики сбора мусора.

Спасибо gccapacity, вы узнаете, какая максимальная емкость в KB of:

  • NGCMX - новое поколение (eden)
  • S0CMX - пространство для оставшихся в живых 0
  • S1CMX - пространство для оставшихся в живых 0
  • OGCMX - Максимальное старое поколение