Gdzie znajduje się pula stałych String Java - sterta czy stos?

104

Znam koncepcję puli stałych i puli stałych String używanej przez maszyny JVM do obsługi literałów String. Ale nie wiem, jaki typ pamięci jest używany przez maszynę JVM do przechowywania literałów stałych typu String. Stos czy sterta? Ponieważ jest to literał, który nie jest powiązany z żadną instancją, zakładam, że będzie przechowywany w stosie. Ale jeśli nie odwołuje się do niego żadna instancja, literał musi zostać zebrany przez uruchomienie GC (popraw mnie, jeśli się mylę), więc jak to jest obsługiwane, jeśli jest przechowywany na stosie?

Rengasami Ramanujam
źródło
11
Jak można przechowywać pulę na stosie? czy znasz pojęcie stosu?
The Scrum Meister
1
Cześć Meister Scrum, starałem się powiedzieć, że to niemożliwe. Przepraszam za złą konwencję. Odnośnie GC Dopiero teraz się dowiedziałem. Dzięki za to
Rengasami Ramanujam,
@TheScrumMeister - w rzeczywistości w pewnych okolicznościach mogą być zbierane jako śmieci. „Łamacz umowy” polega na tym, że obiekt kodu dla dowolnej klasy, która wspomina o literale ciągu, będzie miał odniesienie do obiektu String, który reprezentuje literał.
Stephen C,

Odpowiedzi:

74

Technicznie nie ma odpowiedzi. Zgodnie ze specyfikacją maszyny wirtualnej języka Java obszar przechowywania literałów łańcuchowych znajduje się w puli stałych czasu wykonywania . Obszar pamięci stałej puli czasu wykonywania jest przydzielany na podstawie klasy lub interfejsu, więc nie jest w ogóle powiązany z żadnymi instancjami obiektów. Pula stałych czasu wykonywania jest podzbiorem obszaru metod, który „przechowuje struktury klas, takie jak pula stałych czasu wykonywania, dane pól i metod oraz kod metod i konstruktorów, w tym metody specjalne używane podczas inicjowania i interfejsu klas i instancji inicjalizacja typu ”. Specyfikacja VM mówi, że chociaż obszar metody jest logicznie częścią sterty, nie narzuca, że ​​pamięć przydzielona w obszarze metody podlega czyszczeniu pamięci lub innym zachowaniom, które byłyby związane z normalnymi strukturami danych przydzielonymi do sterty.

Duane Moore
źródło
8
W rzeczywistości, gdy klasy są ładowane do maszyny wirtualnej, stałe łańcuchowe zostaną skopiowane do sterty, do puli ciągów o szerokości maszyny wirtualnej (w permgen, jak powiedział Stephen C), ponieważ równe literały ciągów w różnych klasach muszą być ten sam obiekt String (przez JLS).
Paŭlo Ebermann
1
Dziękuję wszystkim za odpowiedzi. Wiele zrozumiałem podczas tej dyskusji. Miło was poznać :)
Rengasami Ramanujam
4
Paŭlo, to prawda dla maszyny wirtualnej firmy Sun, ale niekoniecznie dla wszystkich implementacji JVM. Jak wspomniano w specyfikacji JVM, chociaż pula stałych czasu wykonywania i obszar metod są logicznie częścią sterty, nie muszą zachowywać się tak samo. Tylko niewielka różnica semantyczna, naprawdę :)
Duane Moore,
54

Jak wyjaśniono w tej odpowiedzi , dokładna lokalizacja puli ciągów nie jest określona i może się różnić w zależności od implementacji maszyny JVM.

Warto zauważyć, że do wersji Java 7 pula znajdowała się w obszarze permgen stosu w wirtualnej maszynie wirtualnej hotspot, ale została przeniesiona do głównej części sterty od wersji Java 7 :

Obszar : HotSpot
Streszczenie : W JDK 7, internowane ciągi nie są już alokowane w stałej generacji stosu Java, ale zamiast tego są przydzielane w głównej części stercie Java (znanej jako młode i stare pokolenia) wraz z innymi obiekty utworzone przez aplikację. Ta zmiana spowoduje, że więcej danych będzie znajdować się w głównej stercie Java, a mniej danych w trwałym generowaniu, a zatem może wymagać dostosowania rozmiarów sterty. Większość aplikacji zauważy tylko stosunkowo niewielkie różnice w wykorzystaniu sterty z powodu tej zmiany, ale większe aplikacje, które ładują wiele klas lub intensywnie korzystają z metody String.intern (), zobaczą bardziej znaczące różnice. RFE: 6962931

A w Java 8 Hotspot, Permanent Generation zostało całkowicie usunięte.

asylias
źródło
30

Literały ciągów nie są przechowywane na stosie. Nigdy. W rzeczywistości na stosie nie są przechowywane żadne obiekty.

Literały ciągów znaków (lub dokładniej, obiekty, które je reprezentują String) historycznie przechowywane w stercie zwany „PermGen” sterty. (Permgen to skrót od permanentnego generowania).

W normalnych okolicznościach literały String i wiele innych rzeczy w stercie permgen jest „trwale” osiągalne i nie są zbierane jako śmieci. (Na przykład literały ciągów są zawsze dostępne z obiektów kodu, które ich używają). Można jednak skonfigurować maszynę JVM tak, aby próbowała znaleźć i zebrać klasy ładowane dynamicznie, które nie są już potrzebne, co może spowodować, że literały String zostaną wyrzucone do pamięci. .

WYJAŚNIENIE # 1 - Nie mówię, że Permgen nie otrzymuje GC. Dzieje się tak, zazwyczaj gdy maszyna JVM decyduje się na uruchomienie pełnego GC. Chodzi mi o to, że literały String będą osiągalne tak długo, jak długo kod, który ich używa, będzie osiągalny, a kod będzie osiągalny tak długo, jak długo jest osiągalny moduł ładujący klasy kodu, a dla domyślnych klas ładujących oznacza to „na zawsze”.

WYJAŚNIENIE # 2 - W rzeczywistości Java 7 i nowsze wersje używają zwykłej sterty do przechowywania puli ciągów. W ten sposób obiekty String, które reprezentują literały String i łańcuchy intern'd, znajdują się w rzeczywistości w zwykłej stercie. (Zobacz odpowiedź @ assylias, aby uzyskać szczegółowe informacje).


Ale wciąż próbuję znaleźć cienką linię między przechowywaniem literału ciągu i ciągu utworzonego za pomocą new.

Nie ma „cienkiej linii”. To naprawdę bardzo proste:

  • String obiekty, które reprezentują / odpowiadają literałom ciągów, są przechowywane w puli ciągów.
  • Stringobiekty utworzone przez String::internwywołanie są przechowywane w puli ciągów.
  • Wszystkie inne Stringprzedmioty NIE są trzymane w puli strun.

Następnie pojawia się oddzielne pytanie, gdzie pula ciągów jest „przechowywana”. Przed Java 7 był to stos permgen. Od wersji Java 7 jest to główna kupa.

Stephen C.
źródło
23

Łączenie strun

Pule ciągów (czasami nazywane również kanonikalizacją ciągów) to proces zastępowania kilku obiektów String o równej wartości, ale o różnej tożsamości, jednym wspólnym obiektem String. Możesz osiągnąć ten cel, zachowując własną mapę (z możliwie słabymi lub słabymi odniesieniami w zależności od wymagań) i używając wartości mapy jako wartości kanonizowanych. Lub możesz użyć metody String.intern (), która jest dostarczana przez JDK.

W czasach Javy 6 używanie String.intern () było zabronione przez wiele standardów ze względu na dużą możliwość uzyskania wyjątku OutOfMemoryException, jeśli pule wymknęły się spod kontroli. Implementacja puli ciągów w Oracle Java 7 została znacznie zmieniona. Szczegóły można znaleźć w http://bugs.sun.com/view_bug.do?bug_id=6962931 i http://bugs.sun.com/view_bug.do?bug_id=6962930 .

String.intern () w Javie 6

W tamtych dobrych czasach wszystkie internowane ciągi były przechowywane w PermGen - części sterty o stałym rozmiarze używanej głównie do przechowywania załadowanych klas i puli ciągów. Oprócz jawnie wbudowanych ciągów, pula ciągów PermGen zawierała również wszystkie ciągi literałów używane wcześniej w twoim programie (użyte jest tutaj ważne słowo - jeśli klasa lub metoda nigdy nie została załadowana / wywołana, żadne zdefiniowane w niej stałe nie zostaną załadowane).

Największym problemem związanym z taką pulą ciągów w Javie 6 była jej lokalizacja - PermGen. PermGen ma stały rozmiar i nie można go rozszerzać w czasie wykonywania. Możesz to ustawić za pomocą opcji -XX: MaxPermSize = 96m. O ile wiem, domyślny rozmiar PermGen waha się między 32M a 96M w zależności od platformy. Możesz zwiększyć jego rozmiar, ale jego rozmiar zostanie ustalony. Takie ograniczenie wymagało bardzo ostrożnego korzystania z String.intern - lepiej nie internować żadnych niekontrolowanych danych wejściowych użytkownika za pomocą tej metody. Z tego powodu pulowanie ciągów w czasach Java 6 było głównie implementowane w mapach zarządzanych ręcznie.

String.intern () w Javie 7

Inżynierowie Oracle dokonali niezwykle ważnej zmiany w logice buforowania ciągów w Javie 7 - pula ciągów została przeniesiona na stertę. Oznacza to, że nie jesteś już ograniczony przez oddzielny obszar pamięci o stałym rozmiarze. Wszystkie ciągi znaków znajdują się teraz w stercie, podobnie jak większość innych zwykłych obiektów, co pozwala na zarządzanie tylko rozmiarem sterty podczas dostosowywania aplikacji. Z technicznego punktu widzenia już samo to może być wystarczającym powodem do ponownego rozważenia użycia String.intern () w programach Java 7. Ale są inne powody.

Wartości puli ciągów są zbierane jako elementy bezużyteczne

Tak, wszystkie ciągi w puli ciągów maszyny JVM kwalifikują się do czyszczenia pamięci, jeśli nie ma do nich odniesień z katalogu głównego programu. Dotyczy to wszystkich omawianych wersji Javy. Oznacza to, że jeśli Twój wewnętrzny ciąg wyszedł poza zakres i nie ma innych odniesień do niego - zostanie on usunięty z puli ciągów JVM.

Kwalifikując się do wyrzucania elementów bezużytecznych i rezydując w stercie, pula ciągów JVM wydaje się być właściwym miejscem dla wszystkich twoich ciągów, prawda? W teorii to prawda - nieużywane łańcuchy będą zbierane jako śmieci z puli, użyte łańcuchy pozwolą ci zaoszczędzić pamięć na wypadek, gdybyś otrzymał równy ciąg z wejścia. Wydaje się, że jest to idealna strategia oszczędzania pamięci? Prawie tak. Przed podjęciem jakichkolwiek decyzji musisz wiedzieć, jak jest zaimplementowana pula ciągów.

źródło.

Próbować
źródło
11

Jak wyjaśniają inne odpowiedzi, pamięć w Javie jest podzielona na dwie części

1. Stos: jeden stos jest tworzony na wątek i przechowuje ramki stosu, które ponownie przechowują zmienne lokalne, a jeśli zmienna jest typem referencyjnym, wówczas ta zmienna odnosi się do miejsca w pamięci w stercie dla rzeczywistego obiektu.

2. Sterta: Wszystkie rodzaje obiektów będą tworzone tylko w stercie.

Pamięć sterty jest ponownie podzielona na 3 części

1. Young Generation: Przechowuje przedmioty, które mają krótkie życie. Samo Young Generation można podzielić na dwie kategorie Eden Space i Survivor Space .

2. Stara generacja: Przechowuj obiekty, które przetrwały wiele cykli czyszczenia pamięci i nadal są przywoływane.

3. Trwałe generowanie: Przechowuje metadane dotyczące programu, np. Pula stałych czasu wykonywania.

Pula stałych ciągów należy do obszaru stałego generowania pamięci sterty.

Możemy zobaczyć pulę stałych czasu wykonywania dla naszego kodu w kodzie bajtowym, używając, javap -verbose class_namektóre pokaże nam referencje do metod (#Methodref), obiekty klas (#Class), literały ciągów (#String)

pula-stała-runtime

Możesz przeczytać więcej na ten temat w moim artykule Jak JVM obsługuje wewnętrzne przeciążanie i zastępowanie metod .

Naresh Joshi
źródło
Proszę ujawnić wszelkie powiązania i nie wykorzystywać tej witryny do promowania swojej witryny poprzez publikowanie. Zobacz: Jak napisać dobrą odpowiedź? .
9

Do świetnych odpowiedzi, które już tu zawarłem, chcę dodać coś, czego mi brakuje - ilustrację.

Jak już masz, JVM dzieli przydzieloną pamięć dla programu Java na dwie części. jeden to stos, a drugi to sterta . Stos jest używany do celów wykonania, a sterta jest używana do przechowywania. W tej pamięci sterty JVM przydziela trochę pamięci przeznaczonej specjalnie dla literałów łańcuchowych. Ta część pamięci sterty jest nazywana pulą stałych ciągów .

Na przykład, jeśli zainicjujesz następujące obiekty:

String s1 = "abc"; 
String s2 = "123";
String obj1 = new String("abc");
String obj2 = new String("def");
String obj3 = new String("456);

Literały łańcuchowe s1i s2przejdą do puli stałych łańcuchów, obiektów obj1, obj2, obj3 do sterty. Wszystkie z nich będą przywoływane ze stosu.

Należy również zauważyć, że „abc” pojawi się w stercie i puli stałych łańcuchowych. Dlaczego jest String s1 = "abc"i String obj1 = new String("abc")będzie tworzone w ten sposób? Dzieje się tak, ponieważ String obj1 = new String("abc")jawnie tworzy nowe i referencyjnie odrębne wystąpienie obiektu String i String s1 = "abc"może ponownie użyć wystąpienia z puli stałych ciągów, jeśli jest dostępna. Bardziej szczegółowe wyjaśnienie: https://stackoverflow.com/a/3298542/2811258

wprowadź opis obrazu tutaj

Jasio
źródło
Na podanym diagramie, gdzie istniałyby literały „def” i „456”. Jak by się do nich odnosić?
Satyendra
Dzięki za komentarz @Satyendra, zaktualizowałem ilustrację i odpowiedź.
Johnny
@Stas, dlaczego tworzony jest inny obiekt String „abc”… powinien używać odniesienia obj1, aby wskazać literał w prawo?
Dzieje się tak, ponieważ String obj1 = new String ("abc") jawnie tworzy nową i referencyjnie odrębną instancję obiektu String, a String s1 = "abc" może ponownie wykorzystać instancję z puli stałych łańcuchowych, jeśli taka jest dostępna. Bardziej szczegółowe wyjaśnienie: stackoverflow.com/a/3298542/2811258
Johnny