System zarządzania pamięcią w Javie opiera się na dwóch konceptach: Stacku czyli Stosie oraz Heap, a więc Kopcu. Przyjrzyjmy się tym dwóm strukturom danych nieco bliżej.
Stack i wątki
W ramach działania aplikacji Javowej, począwszy od metody main
, na Stacku (Stosie) pojawiają się jedna na drugiej ramki zawierające zmienne metod. Przestrzegają przy tym zasady LIFO (Last In, First Out). Gdy dana dana metoda zakończy swoje działanie, to jest automatycznie wypychana ze Stacka.
Każdy wątek ma swój Thread Stack
, na którym przechowywane są zmienne lokalne, ale jeśli są to zmienne referencyjne, to – uwaga – same obiekty do których odnoszą się te zmienne, znajdują się na Kopcu.
Załóżmy dla przykładu, że dana klasa implementująca interfejs Runnable ma jakieś pole value
typu Integer
. Wtedy jeśli z tej klasy zostaną utworzone np. 2 wątki, to każdy z tych wątków będzie korzystał z tego samego obiektu do którego odnosi się zmienna value
. Dzieje się tak właśnie dlatego, że ten obiekt znajduje się na Heap, a nie lokalnych stackach tych wątków.
Ale jeśli dodatkowo ta sama klasa ma jakąś metodę, która będzie miała parametr int i
, to kopię tego samego parametru i
będą miały obydwa wątki, każdy na swoim lokalnym Stacku.
Kopiec
Jak już wspominałem wyżej – wszystkie obiekty w Javie znajdują się na Kopcu.
W cyklu działania danej aplikacji, obiekty mogą należeć do różnych tzw. Generations. Wyróżnia się kilka Generations obiektów i to determinuje ich miejsce w Kopcu.
Young Generation:
Eden Space – tu trafiają nowo utworzone obiekty.
Survivor Space – tu trafiają obiekty, które przetrwają cykl Garbage Collection w Eden Space.
Old Generation:
Tenured Space – tu trafiają obiekty, które przetrwały jakiś czas w Survivor Space.
Permanent Generation/Metaspace:
Miejsce w pamięci, które zawiera różne metadane a także static metody oraz zmienne.
UWAGA: Od Javy 8 to miejsce nazywa się Metaspace i różni się tym of Permanent Generation, że dynamicznie zmienia swój rozmiar w runtime aplikacji, czego Permanent Generation nie potrafiło.
Jak widać obiekty i ich miejsce w Kopcu jest też ściśle związane z Garbage Collectorem, ale o tym w innym wpisie 🙂
Różnica między Stosem i Kopcem
Pomijając oczywiste różnice i informacje, które opisałem powyżej, Stack i Heap różnią się od siebie jeszcze pod kilkoma innymi względami:
- zmienne w Stacku mają długość życia przypisaną do trwania metody, a obiekty w Kopcu zależą od Garbage Collectora i ew. mogą żyć przez całą długość życia aplikacji,*
- dostęp do zmiennych w Stacku jest o wiele szybszy niż do obiektów w Kopcu,
- Heap nie jest Thread Safe i trzeba odpowiednio zarządzać dostępem do obiektów.
*To samo teoretycznie może dotyczyć jakiejś zmiennej znajdującej się na końcu metody main
, ale… zakładam że to dość rzadki przypadek 🙂
String Pool
String Pool przed Javą 7 był częścią Permanent Generation. Od Javy 7 String Pool jest częścią Heap space.
Stringi w Javie są immutable, to znaczy, że jeśli napiszemy:
String s1 = "Test"; String s2 = "Test";
To Java nie przydzieli dodatkowego miejsca w String Pool dla stringa, na którego wskazuje zmienna “s2”. Tylko “s1” i “s2” będą wskazywały na to samo miejsce w pamięci.
UWAGA: Wyjątkiem jest sytuacja, kiedy stworzymy nowego Stringa za pomocą słowa kluczowego “new”, np.:
String s1 = "Test"; String s2 = new String("Test");
Wtedy nawet jeśli taki literał już istnieje w String Pool, to Java utworzy nowy obiekt typu String i umieści go na Heap.
Zaletą String Poola w Heap Space jest to, że Stringi które nie są z niczym powiązane (unreferenced Strings) podlegają usunięciu przez Garbage Collector. A w przypadku Permanent Generation przed Javą 7 nie były usuwane, bo.. były częścią Permanent Generation, której nie tykał się Garbage Collector. A jako że Permanent Generation było obszarem o stałym rozmiarze, to można było dość łatwo osiągnąć Out of Memory Error
przez tworzenie zbyt dużej ilości nowych obiektów typu Stringów.