Если строки Java неизменяемы, а StringBuilder изменчив, почему они тратят одинаковое количество памяти в моем коде? - PullRequest
5 голосов
/ 19 октября 2010

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

Использование строки:

while(true)
{
    String s = String.valueOf(System.currentTimeMillis());
    System.out.println(s);
    Thread.sleep(10);
}

Использование StringBuilder:

StringBuilder s = null;
    while(true)
    {
        s = new StringBuilder();
        s.append(System.currentTimeInMillis());
        System.out.println(s);
        Thread.sleep(10);
    }

В обоих случаях они застряли в 12540 K растрате памяти. Запуск этого теста в Windows XP SP2.

Почему они тратят одинаковое количество памяти? Почему неизменная строка перестала тратить память? Не по теме: Как я могу преобразовать StringBuilder в байтовый массив, закодированный в определенной кодировке?

Ответы [ 6 ]

9 голосов
/ 19 октября 2010

Трудно понять, что вы на самом деле спрашиваете здесь, но приложение ведет себя именно так, как я и ожидал.

Строки являются неизменяемыми, и сборщик мусора их не выводит.не так ли

Как изменяемые, так и неизменяемые объекты могут собираться мусором в Java.

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

В обоих ваших приложениях объекты примерно одинакового размера создаются один раз каждые 10миллисекунды.На каждой итерации создается новый объект, и его ссылка присваивается s, заменяя предыдущую ссылку.Это делает предыдущий объект недоступным и пригодным для сборки мусора.В какой-то момент Java VM решает запустить сборщик мусора.Это избавляет от всего недоступного объекта ... и приложение продолжается.

Я читал, что общие строки никогда не собираются сборщиком мусора, не так ли?

Это ложно по двум причинам:

  • Строки, созданные new String(...), String.substring(...) 1 и т. Д., Не являютсяотличается от любого другого объекта Java.

  • Строки, которые интернированы (путем вызова String.intern()), хранятся в пуле строк, который содержится в куче PermGen 2 .Однако даже куча PermGen собирается мусором, хотя и в более длительные сроки, чем куча, в которой обычно создаются объекты.

(Когда-то куча PermGen не собиралась,но это было изменено давно.)

Как правильно идентифицировал @MichaelBorgwardt, вы путали строковые объекты (в общем) со строковыми объектами, которые соответствуют строковым литералам.Последние интернируются автоматически и попадают в пул строк.Тем не менее, они все еще могут быть предметом сбора мусора.Это может произойти, если родительский класс выгружен и ничто иное не ссылается на литерал.


1 - В Java 6 и более ранних версиях есть разница между строками, созданными с использованием new String и использованием String.substring.В последнем случае исходная строка и подстрока будут иметь общий массив, содержащий символы строки.В Java 7 это изменилось.String.substring теперь создает новый резервный массив.

2 - Начиная с Java 7, пул строк - это просто (скрытая) структура данных в обычной куче.Начиная с Java 8, куча PermGen больше не существует.

6 голосов
/ 19 октября 2010

Вы путаете две совершенно разные вещи:

  • Строки являются неизменяемыми, но это не имеет никакого отношения к тому, являются ли они мусором или нет.Тем не менее, это означает, что если вам нужно внести много изменений в строку (например, создать большую строку, добавляя по одному символу за раз), то в конечном итоге вы сделаете много копий и большую работу для сборщика мусора..
  • String literals (т.е. строки, которые пишутся непосредственно в исходном коде) являются частью пула интернированных строк и, как правило, не являются сборщиком мусора.Однако это делается для того, чтобы несколько экземпляров одной и той же строки в исходном коде могли быть заменены ссылками на один и тот же объект, что может сэкономить много места.И это возможно только потому, что строки являются неизменяемыми, поэтому две части программы, содержащие ссылку на одну и ту же строку, не могут мешать друг другу.
4 голосов
/ 19 октября 2010
  1. Похоже, вы предполагаете, что изменяемый класс будет тратить больше памяти, чем не изменяемый класс. Я не понимаю почему.

  2. Ваш код неверен, если он предназначен для выделения большего количества памяти в каждом цикле. Он просто присваивает ссылку s новому объекту, поэтому предыдущий объект теряется и будет в конечном итоге собираться мусором.

  3. Просмотр памяти ОС для JVM - это очень грубая / неточная оценка выделенной памяти Java.

  4. StringBuilder и String (и StringBuffer и char []) все эффективны, они выделяют приблизительно 2 байта на символ (Java использует некоторую вариацию UTF-16) плюс небольшой (незначительный для больших строк) заголовок. *

3 голосов
/ 19 октября 2010

Потому что вы строите и бросаете.Фактически, вы на самом деле не строите какую-либо строку, используя StringBuilder.Обратите внимание, что вы создаете новый объект StringBuilder за один раз.

2 голосов
/ 19 октября 2010

Как уже объяснено, поскольку вы не изменяете строку, а просто указываете s на новое значение; старое значение должно быть мусором. Вот фрагмент кода, использующий stringBuffer, чтобы попытаться фактически изменить значение, на которое указывает s.

StringBuffer s = new StringBuffer();
    while(true)
    {
        s.replace(0,13,Long.toString(System.currentTimeMillis()));
        System.out.println(s);
        Thread.sleep(10);
    }

Следует отметить, что это не решает проблему из-за двух вещей. Во-первых, мы должны каждый раз создавать новую строку, используя Long.toString (), а во-вторых, так как будет вызвана s.toString () это создаст новую String, разделяющую значение stringBuffer (по крайней мере, так было в прошлый раз, когда я проверял). Когда мы сделаем s.replace, он выделит новый массив для хранения этой новой строки, чтобы сохранить неизменяемость строки.

На самом деле, в этом тривиальном случае лучшее, что вы можете сделать (насколько я знаю):

while(true)
    {
        System.out.println(Long.toString(System.currentTimeMillis()));
        Thread.sleep(10);
    }
1 голос
/ 19 октября 2010

Хотел опубликовать это как ответ Стивену С., но по какой-то причине не могу;так что здесь есть пояснение ...

String.subString (...) НЕ создает новую строку.Он ссылается на точку в существующей строке, и возвращение значений подстроки является одним из надежных способов вызвать утечки памяти в вашем приложении (особенно если создается список строк на основе значений подстроки другого списка строк).

Наилучшеепрактика в этом случае:

return new String(s.subString(...));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...