Где находится постоянный пул Java String, в куче или стеке?

104

Я знаю концепцию пула констант и пула констант String, используемых JVM для обработки литералов String. Но я не знаю, какой тип памяти используется JVM для хранения константных литералов String. Стек или куча? Поскольку это литерал, который не связан ни с одним экземпляром, я предполагаю, что он будет храниться в стеке. Но если на него не ссылается ни один экземпляр, литерал должен быть собран с помощью запуска GC (поправьте меня, если я ошибаюсь), так как это обрабатывается, если он хранится в стеке?

Ренгасами Рамануджам
источник
11
Как можно хранить пул в стеке? вы знаете концепцию стека?
The Scrum Meister
1
Привет, Scrum Meister, я хотел сказать, что этого не может быть. Извините за неправильное соглашение. По поводу GC Только сейчас я узнал. Спасибо за это
Ренгасами Рамануджам
@TheScrumMeister - на самом деле, при некоторых обстоятельствах они могут быть собраны мусором. «Прерывание сделки» состоит в том, что объект кода для любого класса, в котором упоминается строковый литерал, будет иметь ссылку на объект String, представляющий литерал.
Stephen C

Ответы:

74

Технически ответ отрицательный. Согласно спецификации виртуальной машины Java, область для хранения строковых литералов находится в пуле констант времени выполнения . Область памяти пула констант времени выполнения выделяется для каждого класса или интерфейса, поэтому она вообще не привязана к каким-либо экземплярам объекта. Пул констант времени выполнения - это подмножество области методов, в которой «хранятся структуры для каждого класса, такие как пул констант времени выполнения, данные полей и методов, а также код для методов и конструкторов, включая специальные методы, используемые при инициализации классов и экземпляров и интерфейсе. инициализация типа ". В спецификации виртуальной машины говорится, что хотя область метода логически является частью кучи, он не требует, чтобы память, выделенная в области метода, подвергалась сборке мусора или другому поведению, которое было бы связано с обычными структурами данных, выделенными для кучи.

Дуэйн Мур
источник
8
На самом деле, когда классы загружаются в виртуальную машину, строковые константы будут скопированы в кучу, в пул строк для всей виртуальной машины (в permgen, как сказал Стивен С.), поскольку равные строковые литералы в разных классах должны быть тот же объект String (JLS).
Паоло Эберманн
1
Спасибо всем за ответы. Я многое понял из этого обсуждения. Приятно знать вас, ребята :)
Ренгасами Рамануджам
4
Palo, это верно для виртуальной машины Sun, но не обязательно верно для всех реализаций JVM. Как упоминается в спецификации JVM, хотя пул констант времени выполнения и область методов логически являются частью кучи, они не должны иметь такое же поведение. Просто небольшая смысловая разница, правда :)
Дуэйн Мур
stackoverflow.com/questions/28107739/…
Канагавелу Сугумар
54

Как объясняется в этом ответе , точное местоположение пула строк не указано и может варьироваться от одной реализации JVM к другой.

Интересно отметить, что до Java 7 пул находился в перманентном пространстве кучи на JVM горячей точки, но с Java 7 он был перемещен в основную часть кучи :

Область : HotSpot
Краткое содержание : В JDK 7 интернированные строки больше не выделяются в постоянной генерации кучи Java, а вместо этого выделяются в основной части кучи Java (известной как молодые и старые поколения) вместе с другими объекты, созданные приложением. Это изменение приведет к тому, что больше данных будет находиться в основной куче Java и меньше данных в постоянной генерации, и, следовательно, может потребоваться корректировка размеров кучи. Большинство приложений увидят лишь относительно небольшие различия в использовании кучи из-за этого изменения, но более крупные приложения, которые загружают много классов или интенсивно используют метод String.intern (), будут видеть более значительные различия. RFE: 6962931

А в Java 8 Hotspot постоянная генерация была полностью удалена.

ассилий
источник
30

Строковые литералы не хранятся в стеке. Никогда. Фактически в стеке не хранятся никакие объекты.

Строковые литералы (или , точнее, объекты строк , которые представляют их) являются исторически хранятся в Heap называется «PermGen» куча. (Пермген - сокращение от постоянного поколения.)

В обычных условиях строковые литералы и многое другое в куче permgen доступны «постоянно» и не собираются сборщиком мусора. (Например, строковые литералы всегда доступны из объектов кода, которые их используют.) Однако вы можете настроить JVM для попытки найти и собрать динамически загруженные классы, которые больше не нужны, и это может привести к сборке строковых литералов. .

РАЗЪЯСНЕНИЕ №1 - Я не говорю, что Пермген не получает GC. Обычно это происходит, когда JVM решает запустить полный сборщик мусора. Я хочу сказать, что строковые литералы будут доступны до тех пор, пока доступен код, который их использует, и код будет доступен, пока доступен загрузчик классов, а для загрузчиков классов по умолчанию это означает «навсегда».

УТОЧНЕНИЕ №2. Фактически, Java 7 и более поздние версии используют обычную кучу для хранения пула строк. Таким образом, объекты String, представляющие строковые литералы и внутренние строки, фактически находятся в обычной куче. (Подробнее см. Ответ @ assylias.)


Но я все еще пытаюсь найти тонкую грань между хранением строкового литерала и строкой, созданной с помощью new.

Нет никакой «тонкой линии». Это действительно очень просто:

  • String объекты, которые представляют / соответствуют строковым литералам, хранятся в пуле строк.
  • Stringобъекты, созданные String::internвызовом, хранятся в пуле строк.
  • Все остальные Stringобъекты НЕ хранятся в пуле строк.

Затем возникает отдельный вопрос о том, где «хранится» пул строк. До Java 7 это была куча permgen. Начиная с Java 7, это основная куча.

Стивен С
источник
23

Объединение строк

Объединение строк (иногда также называемое канонизацией строк) - это процесс замены нескольких объектов String с одинаковым значением, но с разными идентификаторами, на один общий объект String. Вы можете достичь этой цели, сохранив свою собственную карту (возможно, с мягкими или слабыми ссылками в зависимости от ваших требований) и используя значения карты в качестве канонических значений. Или вы можете использовать метод String.intern (), который вам предоставляет JDK.

Во времена Java 6 использование String.intern () было запрещено многими стандартами из-за высокой вероятности получения OutOfMemoryException, если пул вышел из-под контроля. Реализация пула строк в Oracle Java 7 была значительно изменена. Подробности можно найти на http://bugs.sun.com/view_bug.do?bug_id=6962931 и http://bugs.sun.com/view_bug.do?bug_id=6962930 .

String.intern () в Java 6

В те старые добрые времена все интернированные строки хранились в PermGen - части кучи фиксированного размера, которая в основном использовалась для хранения загруженных классов и пула строк. Помимо явно интернированных строк, пул строк PermGen также содержал все литеральные строки, ранее использовавшиеся в вашей программе (здесь используется важное слово - если класс или метод никогда не загружались / не вызывались, любые определенные в нем константы загружаться не будут).

Самой большой проблемой с таким пулом строк в Java 6 было его местоположение - PermGen. PermGen имеет фиксированный размер и не может быть расширен во время выполнения. Вы можете установить его, используя параметр -XX: MaxPermSize = 96m. Насколько мне известно, размер PermGen по умолчанию варьируется от 32 до 96 МБ в зависимости от платформы. Вы можете увеличить его размер, но его размер останется неизменным. Такое ограничение требует очень осторожного использования String.intern - вам лучше не использовать этот метод для неконтролируемого ввода пользователем. Вот почему пул строк во времена Java 6 в основном реализовывался в картах, управляемых вручную.

String.intern () в Java 7

Инженеры Oracle внесли чрезвычайно важное изменение в логику объединения строк в Java 7 - пул строк был перемещен в кучу. Это означает, что вы больше не ограничены отдельной областью памяти фиксированного размера. Все строки теперь расположены в куче, как и большинство других обычных объектов, что позволяет вам управлять только размером кучи при настройке вашего приложения. Технически, одно это может быть достаточной причиной для пересмотра использования String.intern () в ваших программах на Java 7. Но есть и другие причины.

Значения пула строк собираются сборщиком мусора

Да, все строки в пуле строк JVM имеют право на сборку мусора, если на них нет ссылок из корней вашей программы. Это применимо ко всем обсуждаемым версиям Java. Это означает, что если ваша интернированная строка вышла из области видимости и на нее нет других ссылок - она ​​будет собрана мусором из пула строк JVM.

Имея право на сборку мусора и находясь в куче, пул строк JVM кажется подходящим местом для всех ваших строк, не так ли? Теоретически это правда - неиспользуемые строки будут собираться мусором из пула, используемые строки позволят вам сэкономить память на случай, если вы получите одинаковую строку из ввода. Кажется, идеальная стратегия экономии памяти? Почти так. Вы должны знать, как реализован пул строк, прежде чем принимать какие-либо решения.

источник.

Пытаюсь
источник
11

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

1. Стек: один стек создается для каждого потока, и в нем хранятся кадры стека, которые снова хранят локальные переменные, и если переменная является ссылочным типом, то эта переменная относится к области памяти в куче для фактического объекта.

2. Куча: все типы объектов будут создаваться только в куче.

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

1. Молодое поколение: магазины объекты , которые имеют короткую жизнь, сама по себе молодое поколение , можно разделить на две категории Иден пространства и Survivor пространства .

2. Старое поколение: храните объекты, которые пережили много циклов сборки мусора и до сих пор используются.

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

Пул констант строк относится к области постоянной генерации памяти кучи.

Мы можем увидеть пул констант времени выполнения для нашего кода в байт-коде, используя javap -verbose class_nameкоторый покажет нам ссылки на методы (#Methodref), объекты классов (#Class), строковые литералы (#String)

пул констант времени выполнения

Вы можете узнать больше об этом в моей статье Как JVM обрабатывает перегрузку и переопределение методов изнутри .

Нареш Джоши
источник
Пожалуйста, сообщайте о любой аффилированности и не используйте сайт как способ продвижения вашего сайта посредством публикации. См. Как мне написать хороший ответ? .
9

К отличным ответам, которые уже были здесь включены, я хочу добавить кое-что, чего мне не хватает, - иллюстрацию.

Как вы уже знаете, JVM делит выделенную память для программы Java на две части. один - стек, а другой - куча . Стек используется для выполнения, а куча - для хранения. В этой куче памяти JVM выделяет некоторую память, специально предназначенную для строковых литералов. Эта часть кучи памяти называется пулом строковых констант .

Так, например, если вы инициализируете следующие объекты:

String s1 = "abc"; 
String s2 = "123";
String obj1 = new String("abc");
String obj2 = new String("def");
String obj3 = new String("456);

Строковые литералы s1и s2перейдут в пул строковых констант, объекты obj1, obj2, obj3 - в кучу. На все они будут ссылаться из стека.

Также обратите внимание, что «abc» появится в куче и в пуле строковых констант. Почему так String s1 = "abc"и String obj1 = new String("abc")будет создано? Это связано с тем, что String obj1 = new String("abc")явным образом создается новый и референтно отдельный экземпляр объекта String и String s1 = "abc"может повторно использовать экземпляр из пула строковых констант, если он доступен. Для более подробного объяснения: https://stackoverflow.com/a/3298542/2811258

введите описание изображения здесь

Джонни
источник
На данной диаграмме, где бы существовали литералы def и 456. И как на них ссылаться?
Satyendra
Спасибо за комментарий @Satyendra, я обновил иллюстрацию и ответ.
Джонни
@Stas, почему создается другой объект String "abc" .. он должен использовать ссылку obj1, чтобы указать буквальное право?
Это потому, что String obj1 = new String ("abc") явно создает новый и референциально отличный экземпляр объекта String, а String s1 = "abc" может повторно использовать экземпляр из пула строковых констант, если он доступен. Для более подробного объяснения: stackoverflow.com/a/3298542/2811258
Джонни