Pamięć stosu i sterty w Javie

99

Jak rozumiem, w Javie pamięć stosu zawiera prymitywy i wywołania metod, a pamięć sterty służy do przechowywania obiektów.

Załóżmy, że mam klasę

class A {
       int a ;
       String b;
       //getters and setters
}
  1. Gdzie będzie przechowywany prymityw aw klasie A?

  2. Dlaczego pamięć sterty w ogóle istnieje? Dlaczego nie możemy przechowywać wszystkiego na stosie?

  3. Czy kiedy obiekt zostanie wyrzucony śmieci, stos skojarzony z przedmiotem jest zniszczony?

Vinoth Kumar CM
źródło
1
stackoverflow.com/questions/1056444/is-it-on-the-stack-or-heap wydaje się odpowiadać na twoje pytanie.
S.Lott,
@ S.Lott, tyle że ten dotyczy Javy, a nie C.
Péter Török
@ Péter Török: Uzgodniony. Chociaż przykładowym kodem jest Java, nie ma znacznika wskazującego, że jest to tylko Java. I ogólna zasada powinna obowiązywać zarówno w Javie jak i C. Ponadto, istnieje wiele odpowiedzi na to pytanie dotyczące przepełnienia stosu.
S.Lott,
9
@ SteveHaigh: na tej stronie wszyscy są zbyt zaniepokojeni tym, czy coś tu należy ... Zastanawiam się, jakie dzielenie się tą stroną naprawdę robi z całą tą złośliwą precyzją, czy pytania należą tutaj, czy nie.
Sam Goldberg,

Odpowiedzi:

106

Podstawową różnicą między stosem a stertą jest cykl życia wartości.

Wartości stosu istnieją tylko w zakresie funkcji, w której zostały utworzone. Po zwróceniu są odrzucane.
Wartości sterty istnieją jednak na stosie. Są one tworzone w pewnym momencie i niszczone w innym (przez GC lub ręcznie, w zależności od języka / środowiska wykonawczego).

Teraz Java przechowuje tylko prymityw na stosie. Dzięki temu stos jest mały i pomaga utrzymać małe ramki poszczególnych stosów, umożliwiając w ten sposób więcej zagnieżdżonych wywołań.
Obiekty są tworzone na stercie, a tylko stosy (które z kolei są prymitywami) są przekazywane na stosie.

Jeśli więc utworzysz obiekt, zostanie on umieszczony na stosie wraz ze wszystkimi zmiennymi, które do niego należą, aby mógł pozostać po powrocie wywołania funkcji.

back2dos
źródło
2
„i tylko referencje (które z kolei są prymitywami)” Dlaczego mówimy, że referencje są prymitywami? Czy możesz to wyjaśnić?
Geek
5
@Geek: Ponieważ obowiązuje wspólna definicja pierwotnych typów danych: „typ danych dostarczany przez język programowania jako podstawowy element konstrukcyjny” . Możesz również zauważyć, że odniesienia znajdują się wśród przykładów kanonicznych w dalszej części artykułu .
back2dos
4
@Geek: Jeśli chodzi o dane, możesz zobaczyć dowolny z podstawowych typów danych - w tym odniesienia - jako liczby. Nawet chars są liczbami i mogą być używane zamiennie. Odniesienia to także tylko liczby odnoszące się do adresu pamięci o długości 32 lub 64 bitów (chociaż nie można ich używać jako takich - chyba że się z nimi nie mierzysz sun.misc.Unsafe).
Sune Rasmussen
3
Terminologia tej odpowiedzi jest nieprawidłowa. Zgodnie ze specyfikacją języka Java referencje NIE są prymitywami. Istota tego, co mówi Odpowiedź, jest jednak poprawna. (Chociaż można zrobić argument że referencje są „w pewnym sensie” prymitywny jest przez przez JLS definiuje terminologię dla Javy, a ona mówi, że prymitywne typy. boolean, byte, short, char, int, long, floatI double.)
Stephen C
4
Jak znalazłem w tym artykule , Java może przechowywać obiekty na stosie (a nawet w rejestrach małych obiektów krótkotrwałych). JVM może zrobić całkiem sporo pod przykryciem. Nie jest do końca poprawne stwierdzenie: „Teraz Java przechowuje tylko prymitywy na stosie”.
49

Gdzie są przechowywane prymitywne pola?

Pola pierwotne są przechowywane jako część obiektu, który jest gdzieś utworzony . Najłatwiejszym sposobem wymyślenia, gdzie to jest - jest kupa. Jednak nie zawsze tak jest. Jak opisano w teorii i praktyce Java: Legendy o wydajności w mieście, ponownie :

Maszyny JVM mogą stosować technikę zwaną analizą ucieczki, dzięki której mogą stwierdzić, że niektóre obiekty pozostają ograniczone do jednego wątku przez cały okres ich istnienia, a okres istnienia jest ograniczony przez czas życia danej ramki stosu. Takie obiekty można bezpiecznie przydzielić na stosie zamiast na stosie. Co więcej, w przypadku małych obiektów JVM może całkowicie zoptymalizować alokację i po prostu przenieść pola obiektu do rejestrów.

Zatem poza powiedzeniem „obiekt jest tworzony, a pole też tam jest”, nie można powiedzieć, czy coś jest na stosie, czy na stosie. Zauważ, że w przypadku małych, krótkotrwałych obiektów możliwe jest, że „obiekt” nie będzie istniał w pamięci jako taki i może zamiast tego mieć swoje pola bezpośrednio w rejestrach.

Artykuł kończy się na:

Maszyny JVM są zaskakująco dobre w ustalaniu rzeczy, które zakładaliśmy, że tylko programista może wiedzieć. Pozwalając JVM na wybór między alokacją stosu a alokacją sterty w poszczególnych przypadkach, możemy uzyskać korzyści wydajnościowe alokacji stosu bez zmuszenia programisty do zastanowienia się, czy alokować na stosie, czy na stercie.

Zatem jeśli masz kod, który wygląda następująco:

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

gdzie ...nie pozwala quxna opuszczenie tego zakresu, qux może być zamiast tego przydzielone na stosie. Jest to w rzeczywistości wygrana dla maszyny wirtualnej, ponieważ oznacza, że ​​nie trzeba jej nigdy zbierać śmieci - zniknie, gdy opuści zakres.

Więcej na temat analizy ucieczki z Wikipedii. Dla tych, którzy chcą zagłębić się w dokumenty, Escape Analysis for Java od IBM. Dla tych, którzy pochodzą ze świata C #, może się okazać, że stos jest szczegółem implementacji, a prawda o typach wartości Erica Lipperta dobrze czyta (są one przydatne w przypadku typów Java, ponieważ wiele koncepcji i aspektów jest takich samych lub podobnych) . Dlaczego książki .Net mówią o alokacji pamięci stosu a sterty? również w to wchodzi.

Na czym polega stos i stos

Na stosie

Dlaczego w ogóle stos lub stos? W przypadku rzeczy, które opuszczają zakres, stos może być kosztowny. Rozważ kod:

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

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

void qux(String arg) {
    ...
}

Parametry też są częścią stosu. W sytuacji, gdy nie masz stosu, przekazujesz pełny zestaw wartości na stosie. Jest to w porządku "foo"i małe ciągi ... ale co by się stało, gdyby ktoś umieścił ogromny plik XML w tym ciągu. Każde wywołanie byłoby skopiować cały ogromny ciąg na stosie - a to byłoby dość rozrzutny.

Zamiast tego lepiej jest umieścić obiekty, które mają trochę życia poza bezpośrednim zasięgiem (przekazane do innego zasięgu, utknięte w strukturze, którą ktoś inny utrzymuje itp.) W innym obszarze zwanym stertą.

Na stosie

Nie potrzebujesz stosu. Można hipotetycznie napisać język, który nie używa stosu (o dowolnej głębokości). Tak zrobił stary BASIC, którego nauczyłem się w młodości, można było wykonać tylko 8 poziomów gosubwywołań, a wszystkie zmienne były globalne - nie było stosu.

Zaletą stosu jest to, że gdy masz zmienną, która istnieje z zakresem, kiedy go opuszczasz, ramka stosu jest wyskakująca. To naprawdę upraszcza to, co jest, a czego nie ma. Program przechodzi do innej procedury, nowej ramki stosu; program powraca do procedury, a ty wróciłeś do tego, który widzi twój obecny zakres; program opuszcza procedurę, a wszystkie elementy na stosie zostają zwolnione.

To naprawdę ułatwia życie osobie piszącej środowisko wykonawcze, aby kod używał stosu i stosu. Po prostu wiele koncepcji i sposobów pracy nad kodem pozwala osobie piszącej kod w języku uwolnić się od bezpośredniego myślenia o nim.

Charakter stosu oznacza również, że nie można go podzielić. Fragmentacja pamięci to prawdziwy problem ze stertą. Przydzielasz kilka obiektów, następnie zbierasz śmieci środkowy, a następnie próbujesz znaleźć miejsce na następny duży do przydzielenia. To bałagan. Umieszczenie rzeczy na stosie zamiast tego oznacza, że ​​nie musisz sobie z tym poradzić.

Kiedy coś jest zbierane śmieci

Kiedy coś jest zbierane, znikają. Ale to tylko śmieci są zbierane, ponieważ już o nich zapomniano - nie ma już żadnych odwołań do obiektu w programie, do których można uzyskać dostęp z bieżącego stanu programu.

Zaznaczę, że jest to bardzo duże uproszczenie zbierania śmieci. Istnieje wiele śmieciarek (nawet w Javie - możesz ulepszyć śmieciarza za pomocą różnych flag ( dokumentów ). Zachowują się one inaczej, a niuanse tego, jak każdy z nich robi rzeczy, są nieco zbyt głębokie na tę odpowiedź. Możesz przeczytać Podstawy Java Garbage Collection, aby lepiej zrozumieć, jak niektóre z nich działają.

To powiedziawszy, jeśli coś jest przydzielane na stosie, nie jest to śmieci gromadzone jako część System.gc()- jest ono zwalniane, gdy pojawia się ramka stosu. Jeśli coś jest na stosie i do którego odwołuje się coś na stosie, nie będzie to wtedy śmieci.

Dlaczego to ma znaczenie?

W przeważającej części jego tradycja. Podręczniki pisane, klasy kompilatorów i różne bity dokumentują wiele o kupie i stosie.

Jednak dzisiejsze maszyny wirtualne (JVM i podobne) dołożyły wszelkich starań, aby ukryć to przed programistą. Jeśli nie zabraknie jednego lub drugiego i nie musisz wiedzieć, dlaczego (zamiast odpowiednio zwiększać przestrzeń dyskową), nie ma to większego znaczenia.

Obiekt jest gdzieś i znajduje się w miejscu, w którym można uzyskać do niego szybki i prawidłowy dostęp przez odpowiedni czas, jaki istnieje. Jeśli jest na stosie lub na stosie - to naprawdę nie ma znaczenia.

Społeczność
źródło
7
  1. Na stercie, jako część obiektu, do którego odwołuje się wskaźnik na stosie. to znaczy. aib będą przechowywane obok siebie.
  2. Ponieważ gdyby cała pamięć była pamięcią stosową, nie byłaby już wydajna. Dobrze jest mieć mały, szybko dostępny obszar, w którym zaczynamy, i mieć te referencyjne przedmioty w znacznie większym obszarze pamięci, który pozostaje. Jest to jednak przesada, gdy obiekt jest po prostu pojedynczym prymitywem, który zajmowałby mniej więcej tyle samo miejsca na stosie, co wskaźnik do niego.
  3. Tak.
pdr
źródło
1
Dodam do twojego punktu # 2, że jeśli przechowujesz obiekty na stosie (wyobraź sobie obiekt słownika z setkami tysięcy wpisów), to aby przekazać go lub zwrócić z funkcji, musisz skopiować obiekt co czas. Używając wskaźnika lub odwołania do obiektu w stercie, przekazujemy tylko (małe) odwołanie.
Scott Whitlock,
1
Myślałem, że 3 byłoby „nie”, ponieważ jeśli obiekt jest zbierany w pamięci, to na stosie nie ma odniesienia do niego.
Luciano,
@Luciano - Widzę twój punkt widzenia. Pytanie 3 czytam inaczej. „W tym samym czasie” lub „do tego czasu” jest niejawne. :: wzruszenie ramionami
pdr
3
  1. Na stercie, chyba że Java przydzieli instancję klasy na stosie jako optymalizację po udowodnieniu za pomocą analizy zmiany znaczenia, że ​​nie wpłynie to na semantykę. Jest to jednak szczegół implementacji, więc dla wszystkich praktycznych celów oprócz mikrooptymalizacji odpowiedź brzmi „na stosie”.

  2. Pamięć stosu musi być przydzielana i zwalniana w kolejności od pierwszego do pierwszego. Pamięć stert można przydzielać i zwalniać w dowolnej kolejności.

  3. Kiedy obiekt jest odśmiecany, nie ma już żadnych odnośników wskazujących na niego ze stosu. Gdyby tak było, utrzymaliby obiekt przy życiu. Prymitywy stosu wcale nie są śmieciami, ponieważ są automatycznie niszczone po powrocie funkcji.

dsimcha
źródło
3

Pamięć stosu służy do przechowywania zmiennych lokalnych i wywoływania funkcji.

Podczas gdy pamięć sterty służy do przechowywania obiektów w Javie. Bez względu na to, gdzie obiekt jest tworzony w kodzie.

Gdzie będzie prymitywny aw class Abyć przechowywane?

W tym przypadku prymityw a jest powiązany z obiektem klasy A. Tworzy więc w Pamięci Sterty.

Dlaczego pamięć sterty w ogóle istnieje? Dlaczego nie możemy przechowywać wszystkiego na stosie?

  • Zmienne utworzone na stosie wykroczą poza zakres i zostaną automatycznie zniszczone.
  • Stos jest znacznie szybszy do alokacji w porównaniu do zmiennych na stercie.
  • Zmienne na stosie muszą zostać zniszczone przez Garbage Collector.
  • Wolniej przydzielać w porównaniu do zmiennych na stosie.
  • Użyłbyś stosu, jeśli dokładnie wiesz, ile danych musisz przeznaczyć przed czasem kompilacji i nie jest on zbyt duży. (Prymitywne zmienne lokalne przechowują się w stosie)
  • Używałbyś sterty, jeśli nie wiesz dokładnie, ile danych będziesz potrzebować w czasie wykonywania lub jeśli musisz przydzielić dużo danych.

Czy kiedy obiekt zostanie wyrzucony śmieci, stos skojarzony z przedmiotem jest zniszczony?

Garbage Collector działa w ramach pamięci Heap, więc niszczy obiekty, które nie mają łańcucha referencyjnego od do katalogu głównego.

Premraj
źródło
-1

Dla lepszego zrozumienia (pamięć sterty / stosu):

Obraz pamięci stosu / stosu

Yousha Aleayoub
źródło