Кажется, что для массива Java char требуется больше 2 байтов на char

Когда я запускаю следующую программу (работает с "java -Xmx151M -cp . com.some.package.xmlfun.Main"):

package com.some.package.xmlfun;
public class Main {

    public static void main(String [] args) {
        char [] chars = new char[50 * 1024 * 1024];

    }
}

Мне нужно увеличить максимальную память до 151 М (-Xmx151M). Соответственно, когда я увеличиваю размер массива, необходимо увеличить лимит:

  • 50 * 1024 * 1024 → -Xmx151M
  • 100 * 1024 * 1024 → -Xmx301M
  • 150 * 1024 * 1024 → -Xmx451M

Почему выглядит так, что java требует 3 байта на char вместо 2 байтов, как предполагает документация?

Также, когда я так же создаю массив long, мне кажется, что ему нужно 12 байт в длину, вместо 8, с int ему нужно 6 байт вместо 4. Как правило, он выглядит так: array_size * element_size * 1.5

Компиляция с помощью - javac \com\som\package\xmlfun\\*java

Работа с - java -Xmx151M -cp . com.some.package.xmlfun.Main

Ответы

Ответ 1

В Java HotSpot VM куча разделена на "новое поколение" и "старое поколение". Массив должен находиться в любом из них. Значение отношения по умолчанию для нового/старого поколения равно 2. (что на самом деле обозначает old/new=2)

Итак, с некоторой простой математикой можно показать, что куча 151 МБ может иметь 50.33 МБ нового поколения и 100.67 МБ старого поколения. Также куча 150 МБ имеет ровно 100 МБ старого поколения. Ваш массив + все остальное (например, args) исчерпывает 100 МБ, поэтому создайте OutOfMemoryError.


Я пытался работать с

java -Xms150m -Xmx150m -XX:+PrintGCDetails Main > c.txt

И из c.txt

(...)
Heap
 PSYoungGen      total 44800K, used 3072K (addresses...)
  eden space 38400K, 8% used (...)
  from space 6400K, 0% used (...)
  to   space 6400K, 0% used (...)
 ParOldGen       total 102400K, used 217K (...)
  object space 102400K, 0% used (...)
 PSPermGen       total 21248K, used 2411K (...)
  object space 21248K, 11% used (...)

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

Ответ 2

Я предполагаю, что то, что вы видите, можно легко объяснить тем, как организована куча в JVM.

Когда вы передаете параметр -Xmx в JVM, вы определяете, какой должен быть максимальный размер кучи. Однако он не связан напрямую с максимальным размером массива, который вы можете выделить.

В JVM сборщик мусора отвечает за выделение памяти для объектов и очистку мертвых объектов. Это сборщик мусора, который решает, как он организует кучу.

Обычно у вас есть что-то, называемое пространством Идена, затем два оставшихся в живых и, наконец, поколение. Все они находятся внутри кучи, и GC делит максимальную кучу между ними. Для получения дополнительной информации об этих пулах памяти проверьте этот блестящий ответ: fooobar.com/questions/13629/...

Я не знаю, что такое значения по умолчанию, и они могут действительно зависеть от вашей системы. Я только что проверил (используя sudo jmap PID), как пулы памяти делят кучу в приложении, которое я запускаю в системе с 64-битными Ubuntu и Oracle Java 7. У машины 1,7 ГБ памяти.

В этой конфигурации я передаю только -Xmx в JVM, а GC делит кучу следующим образом:

  • около 27% для пространства Эдена.
  • около 3% для каждого из оставшихся в живых объектов
  • около 67% для поколенного поколения.

Если у вас есть аналогичное распределение, это будет означать, что самый большой смежный блок вашего 151 МБ находится в поколенном поколении и составляет около 100 МБ. Поскольку массив является непрерывным блоком памяти, и вы просто не можете иметь пул объектов в нескольких пулах памяти, он объясняет поведение, которое вы видите.

Вы можете попробовать играть с параметрами сборщика мусора. Проверьте параметры сборщика мусора здесь: http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

Ваши результаты кажутся мне разумными.

Ответ 3

Если вы посмотрите на размер данных (например, с помощью Visual GC), вы увидите, что размер массива действительно равен 2 байтам за char.

Проблема здесь в том, что JVM пытается подогнать весь массив в старом поколении кучи, а размер этого поколения ограничен отношением размеров нового/старого поколения.

Работа с -XX:NewRatio=5 исправит проблему (значение по умолчанию равно 2).

Ответ 4

Я попытаюсь построить ответ Бруно. Я пробовал этот код прямо сейчас:

public static void main(String[] args) throws IOException {
    char [] chars = new char[50 * 1024 * 1024];
    System.out.println(Runtime.getRuntime().freeMemory());
    System.out.println(Runtime.getRuntime().totalMemory());
    System.out.println(Runtime.getRuntime().maxMemory());
}

И результат был:

38156248
143654912
143654912

Очевидно, что 40 МБ были оставлены свободными для некоторых других целей JVM. Мое лучшее предположение было бы для пространства нового поколения.