Стек і купа пам'яті на Java


99

Як я розумію, у Java стек-пам'ять містить примітиви та виклики методів, а пам'ять купи використовується для зберігання об'єктів.

Припустимо, у мене є клас

class A {
       int a ;
       String b;
       //getters and setters
}
  1. Де буде зберігатися примітив aу класі A?

  2. Чому взагалі існує пам'ять купи? Чому ми не можемо зберігати все на стеці?

  3. Коли об’єкт збирає сміття, чи знищується стек, пов’язаний із об’єктом?


1
stackoverflow.com/questions/1056444/is-it-on-the-stack-or-heap, здається, відповідає на ваше запитання.
S.Lott

@ S.Lott, за винятком того, що мова йде про Яву, а не про C.
Péter Török

@ Péter Török: Погодився. Хоча зразок коду - Java, немає тегу, який би вказував, що це лише Java. І загальний принцип повинен також застосовуватися і до Java, як C. Далі, на Stack Overflow є багато відповідей на це питання.
S.Lott

9
@SteveHaigh: на цьому сайті всі занадто переймаються питанням того, чи щось тут належить ... Цікаво, який розумний справді цей сайт отримує з усією прискіпливістю щодо того, належать тут питання чи ні.
Сем Голдберг

Відповіді:


106

Основна різниця між стеком і купами - це життєвий цикл значень.

Значення стека існують лише в межах функції, в якій вони створені. Після повернення вони відкидаються.
Значення купи, однак, існують у купі. Вони створюються в якийсь момент часу і руйнуються в інший (або GC, або вручну, залежно від мови / часу виконання).

Тепер Java зберігає лише примітиви в стеку. Це зберігає стек малим і допомагає зберігати окремі кадри стека невеликими, тим самим дозволяючи більше вкладених дзвінків.
Об'єкти створюються на купі, і тільки стеки (які в свою чергу є примітивами) передаються навколо на стеку.

Отже, якщо ви створюєте об'єкт, він кладеться на купу з усіма змінними, що належать йому, щоб він міг зберігатися після повернення виклику функції.


2
"і лише посилання (які в свою чергу є примітивами)" Чому кажуть, що посилання є примітивом? Ви можете уточнити, будь ласка?
Geek

5
@Geek: Оскільки застосовується загальне визначення примітивних типів даних: "тип даних, що надається мовою програмування як основний будівельний блок" . Ви також можете помітити, що посилання наведені серед канонічних прикладів, поданих нижче у статті .
back2dos

4
@Geek: З точки зору даних, ви можете розглядати будь-який із примітивних типів даних, включаючи посилання, як цифри. Навіть chars - це числа, і їх можна використовувати як взаємозамінні. Посилання також є лише цифрами, що посилаються на адресу пам'яті, довжиною 32 або 64 біти (хоча вони не можуть бути використані як такі - якщо ви не вадитесь sun.misc.Unsafe).
Sune Rasmussen

3
Термінологія цього відповіді неправильна. Відповідно до специфікації мови Java, посилання НЕ примітивні. Однак суть того, що говорить Відповідь, є правильною. ( В той час як ви можете зробити аргумент , що посилання є «в сенсі» примітив по До JLS визначає термінологію для Java, і це говорить про те , що примітивні типи. boolean, byte, short, char, int, long, floatІ double.)
Стівен З

4
Як я виявив у цій статті , Java може зберігати об’єкти в стеку (або навіть у регістрах для невеликих короткоживучих об'єктів). JVM може зробити трохи під кришками. Не зовсім коректно сказати: "Тепер Java зберігає лише примітиви в стеці".

49

Де зберігаються примітивні поля?

Примітивні поля зберігаються як частина об'єкта, який десь інстанціюється . Найпростіший спосіб подумати, де це - куча. Однак це не завжди так. Як описано в теорії та практиці Java: Переглянуті міські легенди про перегляд :

JVM можуть використовувати методику, що називається аналіз втечі, за допомогою якої вони можуть сказати, що певні об'єкти залишаються обмеженими однією ниткою протягом усього свого життя, і що час життя обмежений часом життя заданого кадру стека. Такі об'єкти можна сміливо виділяти на стеку замість купи. Ще краще, що для невеликих об'єктів JVM може повністю оптимізувати розподіл і просто підняти поля об’єкта в регістри.

Таким чином, не кажучи про те, що "об’єкт створений і поле теж є", не можна сказати, чи є щось на купі чи на стеці. Зауважте, що для невеликих короткотривалих об'єктів можливо, що "об'єкт" не буде існувати в пам'яті як такому, а натомість його поля можуть бути розміщені безпосередньо в регістрах.

У статті закінчується:

JVM на диво добре розбирати речі, про які ми припускали, що тільки розробник міг знати. Дозволяючи JVM вибирати між розподілом стеків і розподілом купи в кожному конкретному випадку, ми можемо отримати переваги від продуктивності розподілу стеків, не змушуючи програміста агонізувати, чи слід виділяти на стек чи на купу.

Таким чином, якщо у вас є код, який виглядає так:

void foo(int arg) {
    Bar qux = new Bar(arg);
    ...
}

де ...не дозволяє quxзалишити цю область, замість цього qux може бути виділено на стеку. Це насправді виграш для VM, оскільки це означає, що йому не потрібно ніколи збирати сміття - воно зникне, коли він покине сферу застосування.

Детальніше про аналіз втечі у Вікіпедії. Для тих, хто бажає заглибитися в документи, аналіз втечі для Java від IBM. Для тих, хто походить із світу C #, ви можете знайти детальну інформацію про реалізацію та правду про типи цін Еріка Ліпперта (вони корисні також для типів Java, оскільки багато понять та аспектів однакові чи схожі) . Чому .Net книги говорять про розподіл стека проти купи пам'яті? також переходить до цього.

На віші стека і купи

На купу

Отже, навіщо взагалі стопку або купу? Для речей, які залишають сферу застосування, стек може бути дорогим. Розглянемо код:

void foo(String arg) {
    bar(arg);
    ...
}

void bar(String arg) {
    qux(arg);
    ...
}

void qux(String arg) {
    ...
}

Параметри також є частиною стеку. У ситуації, коли у вас немає купи, ви передаваєте повний набір значень на стек. Це добре для "foo"невеликих рядків ... але що трапиться, якщо хтось помістить величезний XML-файл у цей рядок. Кожен дзвінок копіював би всю стежку на стек - і це було б досить марно.

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

На стеку

Вам не потрібен стек. Можна, гіпотетично, написати мовою, яка не використовує стек (довільної глибини). Старий БАЗОВ, на якому я навчився в молодості, так і зробив, можна було робити лише 8 рівнів gosubвикликів, і всі змінні були глобальними - не було стека.

Перевага стека полягає в тому, що коли у вас є змінна, що існує з областю, коли ви залишаєте це поле, цей кадр стека вискакує. Це дійсно спрощує те, що там, а що ні. Програма переходить до іншої процедури, нового кадру стека; програма повертається до процедури, а ви знову в тій, яка бачить ваш поточний обсяг; програма залишає процедуру, і всі елементи в стеку розміщуються.

Це дійсно полегшує життя людині, яка пише час виконання для коду, щоб використовувати стек і купу. Вони просто багато понять і способів роботи над кодом, що дозволяє людині, яка пише код мовою, звільнитися від думки про них.

Природа стека також означає, що він не може бути фрагментованим. Фрагментація пам’яті є справжньою проблемою в купі. Ви виділяєте кілька об’єктів, потім сміття збираєте середній, а потім намагаєтесь знайти місце для наступного великого, який буде виділено. Його безлад. Бути в змозі помістити речі на стек означає, що вам не доведеться з цим боротися.

Коли щось збирають сміття

Коли щось зібрано сміття, його пішло. Але це лише зібране сміття, оскільки про нього вже забули - більше немає посилань на об’єкт у програмі, до яких можна отримати доступ із поточного стану програми.

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

Однак, якщо щось виділено на стек, воно не є сміттям, яке збирається частиною System.gc()- воно розміщується, коли кадр стека спливає. Якщо щось знаходиться на купі і посилається на щось, що стоїть на стеці, воно не буде збирати сміття на той час.

Чому це має значення?

Здебільшого, його традиція. Написані підручники та класи-компілятори та різноманітна документація по бітах значною мірою стосуються купи та стеку.

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

Об'єкт знаходиться десь і знаходиться в тому місці, де до нього можна отримати правильний та швидкий доступ протягом відповідного часу, який він існує. Якщо його на стеці чи купі - це насправді не має значення.


7
  1. У купі, як частина об'єкта, на яку посилається вказівник у стеку. тобто. a і b зберігатимуться поруч один з одним.
  2. Тому що якби вся пам’ять була стекової пам'яттю, вона вже не буде ефективною. Добре мати невелику область з швидким доступом, з якої ми починаємо і мати ті довідкові елементи у значно більшій області пам'яті, яка залишається. Однак це надмірність, коли об’єкт - це просто один примітив, який займе приблизно стільки ж місця на стеку, скільки і вказівник на нього.
  3. Так.

1
Я додам до вашого пункту №2, що якщо ви зберігаєте об'єкти в стеку (уявіть об’єкт словника з сотнями тисяч записів), то для того, щоб передати його або повернути його з функції, вам потрібно буде копіювати об'єкт кожен час. Використовуючи вказівник або посилання на об’єкт у купі, ми передаємо лише (малу) посилання.
Скотт Вітлок

1
Я подумав, що 3 буде "ні", тому що якщо об'єкт збирається сміттям, то в стеку немає посилання на нього.
Лучано

@Luciano - я бачу вашу думку. Я читаю питання 3 по-різному. "Одночасно" або "до того часу" неявне. :: знизати плечима ::
пдр

3
  1. У купі, якщо Java не виділяє екземпляр класу на стеку як оптимізацію, після того, як через аналіз скасування доведено, що це не вплине на семантику. Це деталізація щодо впровадження, тому для всіх практичних цілей, крім мікрооптимізації, відповідь "на купі".

  2. Пам'ять стека повинна бути розподілена і розміщена останньою в першому порядку. Куча пам'яті може бути розподілена і розміщена в будь-якому порядку.

  3. Коли об’єкт збирається сміттям, більше немає посилань, що вказують на нього зі стека. Якби вони були, вони зберегли б об’єкт живим. Примітивні стеки взагалі не збирають сміття, оскільки вони автоматично знищуються, коли функція повертається.


3

Пам'ять стека використовується для зберігання локальних змінних та виклику функцій.

Хоча купа пам’яті використовується для зберігання об’єктів на Java. Неважливо, де об’єкт створюється в коді.

Де примітиву aв class Aзберігатися?

У цьому випадку примітивний a асоціюється з об'єктом класу A. Так це створюється в Heap Memory.

Чому взагалі існує пам'ять купи? Чому ми не можемо зберігати все на стеці?

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

Коли об’єкт збирає сміття, чи знищується стек, пов’язаний із об’єктом?

Сміттєзбірник працює в межах пам’яті Heap, тому він знищує об’єкти, які не мають ланцюга відліку до кореня.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.