Огромные массивы выходят из памяти, несмотря на наличие достаточного объема памяти

Используя флаг -Xmx1G, чтобы обеспечить кучу одного гигабайта, выполняется следующее:

public class Biggy {
    public static void main(String[] args) {
        int[] array = new int[150 * 1000 * 1000];
    }
}

Массив должен представлять около 600 МБ.

Тем не менее, следующее выбрасывает OutOfMemoryError:

public class Biggy {
    public static void main(String[] args) {
        int[] array = new int[200 * 1000 * 1000];
    }
}

Несмотря на то, что массив должен содержать около 800 МБ и поэтому легко вписывается в память.

Где пропала пропавшая память?

Ответ 1

В Java, как правило, в куче обычно есть несколько областей (и подрегионов). У вас есть молодой и облагороженный регион с большинством коллекционеров. Большие массивы сразу же добавляются в область хранения, однако, исходя из вашего максимального объема памяти, некоторое пространство будет зарезервировано для молодого пространства. Если вы медленно распределите память, эти регионы будут изменять размер, однако большой блок, подобный этому, может просто потерпеть неудачу, как вы видели.

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

BTW: Если у вас такая большая структура, вы можете использовать прямую память.

IntBuffer array = ByteBuffer.allocateDirect(200*1000*1000*4)
                            .order(ByteOrder.nativeOrder()).asIntBuffer();

int a = array.get(n);
array.put(n, a+1);

Его немного утомительно писать, но имеет одно большое преимущество, он почти не использует кучу. (на голове меньше 1 КБ)

Ответ 2

Имеется достаточно памяти, но не как одно непрерывное блок памяти, необходимый для массива.

Можете ли вы использовать другую структуру данных, которая использует меньшие блоки памяти или нескольких меньших массивов?

Например, следующий код работает с -Xmx1G:

public class Biggy {
    public static void main(String[] args) {
        int [][]array = new int[200][];
        for (int i = 0; i < 200; i++) {
                array[i] = new int[1000 * 1000];
                System.out.println("i=" + i);
        }
    }
}

Ответ 3

Память кучи разделяется на три пространства:

  • Старое поколение
  • Оставшееся время жизни
  • Eden Space

При запуске этот объект будет жить в старом поколении и останется здесь некоторое время.

По умолчанию виртуальная машина увеличивает или уменьшает кучу в каждой коллекции, чтобы попытаться сохранить долю свободного пространства для живых объектов в каждой коллекции в определенном диапазоне. Этот целевой диапазон устанавливается в процентах по параметрам -XX: MinHeapFreeRatio = и -XX: MaxHeapFreeRatio =, а общий размер ограничен ниже на -Xms и выше на -Xmx.

Значение по умолчанию в моем jvm равно 30/70, поэтому максимальный размер объекта в старом поколении ограничен (с -Xmx1G) на 700 Мб (кстати, я получаю то же исключение при работе с параметрами jvm по умолчанию).

Однако вы могли бы генерировать числа с помощью jvm-опций. Например, вы могли бы запустите свой класс с параметрами -Xmx1G -XX:NewRatio=10 и new int[200 * 1000 * 1000]; будет успешным.

Из того, что я мог сказать, Java не был предназначен для хранения больших объектов в памяти. Типичное использование памяти в приложении - это график группы относительно небольших объектов, и обычно вы получите OutOfMemoryError только в том случае, если у вас заканчивается пробел во всех пространствах.

Ниже приведены полезные (и интересные для чтения) статьи:

Эргономика в виртуальной машине 5.0 Java [tm]

Настройка коллекции мусора с виртуальной машиной 5.0 Java [tm]