Ответ 1
По сути, вы правы.
A StringBuilder
(точнее, AbstractStringBuilder
) использует char[]
для хранения строкового представления (хотя обычно String
не является char[]
). Хотя Java не гарантирует, что массив действительно хранится в смежной памяти, это, скорее всего, есть. Таким образом, всякий раз, когда добавляются строки к базовому массиву, выделяется новый массив и, если он слишком велик, бросается OutOfMemoryError
.
Действительно, выполнение кода
StringBuilder b = new StringBuilder();
for (int i = 0; i < 7 * Math.pow(10, 8); i++)
b.append("a"); // line 11
генерирует исключение:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at test1.Main.main(Main.java:11)
Когда строка <33 > char[] copy = new char[newLength];
достигается внутри Arrays.copyOf
, исключение создается потому, что для массива размера newLength
недостаточно памяти.
Обратите внимание также на сообщение с ошибкой: "Кучное пространство Java". Это означает, что объект (массив в этом случае) не может быть выделен в куче Java. (Изменить: есть еще одна возможная причина этой ошибки, см. ответ Marco13).
В виртуальной машине Java есть куча, которая является общей для всех потоков виртуальной машины Java. Куча - это область данных времени выполнения, из которой выделяется память для всех экземпляров классов и массивов.
... Память для кучи не обязательно должна быть смежной.
Реализация Java Virtual Machine может предоставить программисту или пользователю контроль над начальным размером кучи, а также, если куча может динамически расширяться или сокращаться, контролировать максимальный и минимальный размер кучи.
Следующее исключительное условие связано с кучей:
- Если для вычисления требуется больше кучи, чем может быть предоставлено системой автоматического управления хранилищем, виртуальная машина Java генерирует
OutOfMemoryError
.
Разбиение массива на меньшие массивы одинакового общего размера позволяет избежать OOME, потому что каждый массив может храниться отдельно в меньшей смежной области. Конечно, вы "платите" за это, указав из каждого массива на следующий.
Сравните приведенный выше код с этим:
static StringBuilder b1 = new StringBuilder();
static StringBuilder b2 = new StringBuilder();
...
static StringBuilder b10 = new StringBuilder();
public static void main(String[] args) {
for (int i = 0; i < Math.pow(10, 8); i++)
b1.append("a");
System.out.println(b1.length());
// ...
for (int i = 0; i < Math.pow(10, 8); i++)
b10.append("a");
System.out.println(b10.length());
}
Выходной сигнал
100000000
100000000
100000000
100000000
100000000
100000000
100000000
100000000
а затем вызывается OOME.
В то время как первая программа не могла выделить больше, чем 7 * Math.pow(10, 8)
ячейки массива, она суммируется как минимум 8 * Math.pow(10, 8)
.
Обратите внимание, что размер кучи можно изменить с помощью параметров инициализации VM, поэтому размер, который будет вызывать OOME, не является постоянным между системами.