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 qux
na 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 gosub
wywoł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.