Стек и куча памяти в 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, за исключением того, что этот рассказ о Яве, а не о С.
Петер Тёрёк
@ Петер Тёрёк: Согласен. Хотя примером кода является Java, нет тега, указывающего, что это только Java. И общий принцип должен применяться как к Java, так и к C. Далее, есть много ответов на этот вопрос о переполнении стека.
S.Lott
9
@SteveHaigh: на этом сайте все слишком озабочены тем, принадлежит ли что-то здесь ... Интересно, что на самом деле интересует этот сайт со всей придиркой к тому, принадлежат ли вопросы здесь или нет.
Сэм Голдберг,

Ответы:

106

Основное различие между стеком и кучей - это жизненный цикл значений.

Значения стека существуют только в пределах функции, в которой они созданы. Как только она возвращается, они отбрасываются.
Однако значения кучи существуют в куче. Они создаются в определенный момент времени и уничтожаются в другой (GC или вручную, в зависимости от языка / времени выполнения).

Теперь Java хранит только примитивы в стеке. Это уменьшает размер стека и помогает сохранить размеры отдельных стековых фреймов, что позволяет использовать больше вложенных вызовов.
Объекты создаются в куче, и в стеке передаются только ссылки (которые, в свою очередь, являются примитивами).

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

back2dos
источник
2
"и только ссылки (которые, в свою очередь, являются примитивами)" Почему говорят, что ссылки являются примитивами? Можете ли вы уточнить, пожалуйста?
Компьютерщик
5
@ Geek: Потому что применяется общее определение примитивных типов данных: «тип данных, предоставляемый языком программирования в качестве основного строительного блока» . Вы также можете заметить, что ссылки перечислены среди канонических примеров ниже в статье .
back2dos
4
@ Geek: С точки зрения данных, вы можете рассматривать любой из примитивных типов данных - включая ссылки - как числа. Четные charчисла являются числами и могут использоваться взаимозаменяемо как таковые. Ссылки также являются просто числами, относящимися к адресу памяти, длиной 32 или 64 бита (хотя они не могут использоваться как таковые - если вы не возитесь sun.misc.Unsafe).
Сьюне Расмуссен
3
Терминология этого ответа неверна. Согласно Спецификации языка Java, ссылки НЕ являются примитивами. Суть того, что говорит ответ, верна. ( В то время как вы можете сделать аргумент , что ссылки являются «в смысле» примитив по К JLS определяет терминологию для Java, и это говорит о том , что примитивные типы. boolean, byte, short, char, int, long, floatИ double.)
Стивен С
4
Как я обнаружил в этой статье , Java может хранить объекты в стеке (или даже в регистрах для небольших недолговечных объектов). JVM может сделать немного под прикрытием. Не совсем правильно говорить: «Теперь Java хранит только примитивы в стеке».
49

Где хранятся примитивные поля?

Примитивные поля хранятся как часть объекта, который где-то создается . Самый простой способ подумать, где это - это куча. Однако это не всегда так. Как описано в теории и практике Java: городские легенды производительности, вновь :

JVM могут использовать метод, называемый escape-анализом, с помощью которого они могут сказать, что определенные объекты остаются ограниченными одним потоком в течение всего их времени жизни, и что время жизни ограничено временем жизни данного стекового фрейма. Такие объекты можно безопасно размещать в стеке, а не в куче. Более того, для небольших объектов JVM может полностью оптимизировать распределение и просто поднять поля объекта в регистры.

Таким образом, помимо высказывания «объект создан, и поле там тоже», нельзя сказать, находится ли что-то в куче или в стеке. Обратите внимание, что для небольших короткоживущих объектов возможно, что «объект» не будет существовать в памяти как таковой, и вместо этого его поля могут быть помещены непосредственно в регистры.

Статья заканчивается:

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

Таким образом, если у вас есть код, который выглядит так:

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

где ...не позволяет quxпокинуть эту область, вместо этого qux может быть размещен в стеке. Это на самом деле выигрыш для виртуальной машины, потому что это означает, что ей не нужно собирать мусор - он исчезнет, ​​когда покинет область видимости.

Подробнее об анализе побега в Википедии. Для тех, кто хочет вникать в статьи, Escape Analysis for 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. В куче, как часть объекта, на который ссылается указатель в стеке. то есть. А и В будут храниться рядом друг с другом.
  2. Потому что, если бы вся память была стековой, она была бы неэффективной. Хорошо иметь небольшую область быстрого доступа, с которой мы начинаем, и иметь эти ссылочные позиции в гораздо большей области памяти, которая остается. Однако это излишне, когда объект представляет собой простой примитив, который занимал бы в стеке столько же места, сколько указатель на него.
  3. Да.
прецизионный самописец
источник
1
Я хотел бы добавить к вашему пункту # 2, что если вы храните объекты в стеке (представьте объект словаря с сотнями тысяч записей), то для того, чтобы передать его или вернуть из функции, вам нужно будет копировать объект каждый раз. время. Используя указатель или ссылку на объект в куче, мы передаем только (маленькую) ссылку.
Скотт Уитлок
1
Я подумал, что 3 будет «нет», потому что, если объект собирает мусор, то в стеке нет ссылки, указывающей на него.
Лучано
@Luciano - я понимаю твою точку зрения. Я читаю вопрос 3 по-разному. «В то же время» или «к тому времени» неявно. :: Shrug ::
Pdr
3
  1. В куче, если только Java не выделяет экземпляр класса в стеке в качестве оптимизации после подтверждения с помощью escape-анализа, что это не повлияет на семантику. Однако это деталь реализации, поэтому для всех практических целей, кроме микрооптимизации, ответ «в куче».

  2. Память стека должна быть выделена и освобождена последней в порядке очереди. Память кучи может быть выделена и освобождена в любом порядке.

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

dsimcha
источник
3

Стековая память используется для хранения локальных переменных и вызова функций.

Пока куча памяти используется для хранения объектов в Java. Неважно, где объект создается в коде.

Где примитива aв class Aхраниться?

В этом случае примитив a связан с объектом класса A. Так оно и создается в куче памяти.

Почему куча памяти вообще существует? Почему мы не можем хранить все в стеке?

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

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

Сборщик мусора работает в объеме памяти кучи, поэтому он уничтожает объекты, не имеющие цепочки ссылок, от корня до корня.

Premraj
источник
-1

Для лучшего понимания (память кучи / стека):

Образ памяти кучи / стека

Юша Алеауб
источник