Де живе постійний пул Java String, купа чи стек?


104

Я знаю поняття пула констант і пула постійних струн, використовуваних JVM для обробки літералів String. Але я не знаю, який тип пам'яті використовується JVM для зберігання постійних літералів String. Стек чи купа? Оскільки його літерал, який не пов'язаний з жодним екземпляром, я вважаю, що він буде зберігатися в стеці. Але якщо це не посилається жодним екземпляром, то літерал повинен бути зібраний за допомогою запуску GC (виправте мене, якщо я помиляюся), то як же це обробляється, якщо він зберігається в стеку?


11
Як пул може зберігатися на стеці? чи знаєте ви поняття стека?
The Scrum Meister

1
Привіт, Scrum Meister, я намагався сказати, що цього не може бути. Вибачте за неправильну умову. Щодо GC Щойно я дізнався. Дякую за це
Rengasami Ramanujam

@TheScrumMeister - адже за певних обставин вони можуть збирати сміття. "Вимикач угод" полягає в тому, що об'єкт коду для будь-якого класу, який згадує літеральний рядок, матиме посилання на об'єкт String, який представляє літерал.
Stephen C

Відповіді:


74

Відповідь технічно ні. Згідно зі специфікацією Java Virtual Machine, область для зберігання рядкових літералів знаходиться у постійному пулі часу виконання . Область пам’яті постійного пулу виконуваного часу розподіляється на основі класу або за інтерфейсом, тому вона взагалі не прив’язана до жодних екземплярів об'єкта. Постійний пул виконання - це підмножина області методів, яка "зберігає структури класу, такі як постійний пул виконання, дані поля та методу та код для методів та конструкторів, включаючи спеціальні методи, що використовуються в ініціалізації класу та екземпляра та інтерфейсі тип ініціалізації ". Специфікація VM говорить, що хоча область методу логічно є частиною купи, це не диктує, що пам'ять, виділена в області методу, підлягає збору сміття або іншим способом поведінки, який би асоціювався з нормальними структурами даних, виділеними в купу.


8
Насправді, коли класи завантажуються у віртуальний комп'ютер, константи рядків будуть скопійовані в купу, до пулу рядків для VM (у пермен, як сказав Стівен С), оскільки рівні строкові літерали в різних класах повинні бути той самий об'єкт String (за JLS).
Paŭlo Ebermann

1
Дякую всім за відповіді. Я дуже зрозумів з цією дискусією. Приємно вам знати, хлопці :)
Rengasami Ramanujam

4
Paŭlo, це стосується віртуальної машини Sun, але не обов'язково для всіх реалізацій JVM. Як зазначає специфікація JVM, хоча область постійного пулу і методу виконання логічно є частиною купи, вони не повинні мати однакову поведінку. Просто незначна смислова різниця, дійсно :)
Дуейн Мур,


54

Як пояснено у цій відповіді , точне розташування пулу рядків не вказано і може змінюватись від однієї реалізації JVM до іншої.

Цікаво відзначити, що до Java 7 пул знаходився в пермгенному просторі купи на точці JVM, але він був переміщений до основної частини купи з Java 7 :

Область :
Синопсис HotSpot : У JDK 7 інтерновані рядки більше не виділяються в постійне покоління купи Java, а натомість виділяються в основній частині кучі Java (відомі як молоді та старі покоління) разом з іншими об’єкти, створені додатком. Ця зміна призведе до збільшення кількості даних, що зберігаються в основній купі Java, і меншої кількості даних у постійному поколінні, і, таким чином, може знадобитися коригування розмірів купи. Більшість додатків побачать лише відносно невеликі відмінності у використанні купи через цю зміну, але більші додатки, які завантажують багато класів або широко використовують метод String.intern (), побачать більш значні відмінності. RFE: 6962931

І в точці Java 8, Постійне покоління повністю видалено.


30

Літеральні рядки не зберігаються у стеку. Ніколи. Насправді жодних об’єктів не зберігається у стеку.

Рядкові літерали (або , точніше, об'єкти рядків , які представляють їх) є історично зберігаються в Heap називається «PermGen» купа. (Пермген короткий для постійного покоління.)

За звичайних обставин, рядкові літерали та значна частина інших речей в купі пермгену "постійно" доступні і не збираються сміттям. (Наприклад, рядкові літерали завжди доступні від об'єктів коду, які їх використовують.) Однак ви можете налаштувати JVM для пошуку та збору динамічно завантажених класів, які більше не потрібні, і це може призвести до того, що рядкові рядки збираються сміттям .

ПОЯСНЕННЯ №1 - Я не кажу, що Пермген не отримує GC'ed. Це, як правило, коли JVM вирішує запустити Full GC. Моя думка полягає в тому, що рядкові літерали будуть доступні до тих пір, поки код, який їх використовує, буде доступний, і код буде доступний до тих пір, поки завантажувач класів коду буде доступний, а для завантажувачів класів за замовчуванням це означає "назавжди".

ПОЯСНЕННЯ №2 - Насправді Java 7 і пізніші версії використовує звичайну купу для утримання пулу рядків. Таким чином, об'єкти String, які представляють літеральні рядки та рядки intern'd, фактично знаходяться у звичайній купі. (Детальніше див. У відповіді @ assylias.)


Але я все ще намагаюся з'ясувати тонку межу між зберіганням рядка-літералу та рядком, створеним за допомогою new.

Немає «тонкої лінії». Це дійсно дуже просто:

  • String об'єкти, які представляють / відповідають літеральним рядкам, зберігаються в пулі рядків.
  • Stringоб'єкти, створені String::internвикликом, зберігаються в пулі рядків.
  • Усі інші Stringоб'єкти НЕ утримуються у пулі рядків.

Тоді виникає окреме питання про те, де "зберігається" рядок рядків. До Java 7 це була купа пермгенів. Від 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 = 96 м. Наскільки мені відомо, розмір 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. Молоде покоління: зберігає об'єкти, які мають коротке життя, саме молоде покоління можна розділити на дві категорії Едемський простір та Космос виживання .

2. Старе покоління: зберігайте предмети, які пережили багато циклів вивезення сміття і на які ще посилаються.

3. Постійне покоління: зберігає метадані про програму, наприклад, постійний пул часу виконання.

Постійний пул струн належить до області постійної генерації пам'яті Heap.

Ми можемо бачити постійний пул виконання для нашого коду в байт-коді, використовуючи 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, я оновив ілюстрацію та відповідь.
Джонні

@Stas чому інший String-об'єкт "abc" створений. Він повинен використовувати посилання obj1, щоб вказати буквальне право?

Це тому, що String obj1 = new String ("abc") явно створює новий і референційно відмітний екземпляр об'єкта String, а String s1 = "abc" може повторно використовувати екземпляр з пулового константного пулу, якщо такий є в наявності. Для більш детального пояснення: stackoverflow.com/a/3298542/2811258
Джонні
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.