KRÓTKI WSTĘP
Niniejszy a artykuł będzie na temat GC(Garbage collection) na przykładzie języka Java. Zaczniemy od podstaw powoli zagłębiając się we wnętrzności naszego śmieciarza oraz pamięci na której on działa. GC wysŧepuje w wielu językach programowania, między innymi: Smalltalk, Python, Ruby, Java, C# czy D(tak, jest to język programowania :)POJĘCIA
Wszystkie zagadnienia będą omawiane pod kątem GC. Na początek aby rozluźnić atmosferę humorystyczny obrazek :)![]() | |
Collection of garbage |
![]() |
Garbage Collection |
STACK
Inaczej stos, przechowuje prymitywne typy, referencje do obiektów oraz metod. Czas życia zmiennych jest określony poprzez zasięg deklaracji zmiennej, czyli np. zmienna lokalna w metodzie, lub w pętli. Jeśli np. metoda zakończy swoje działanie, zmienna zadeklarowana w jej obrębie zostanie usunięta ze stosu. Stack występuje dla każdego wątku(domyślnie aplikacja działa na jednym).HEAP
Inaczej kopiec lub sterta, w jego obrębie są przechowywane obiekty.Zakładam iż czytelnik ma podstawową wiedzę na temat relacji pomiędzy STACK-HEAP, jeśli nie to dopowiem w uproszczeniu, że stos przechowuje referencję do obiektów znajdujących się na kopcu. Natomiast dla osób bardziej wtajemniczonych polecam link, który ładnie obrazuje tę zależność :)
JVM(Java Virtual Machine)
Słowo "new" rezerwuje pamięć na kopcu(heap). Jeśli brakuje pamięci, JVM za pomocą GB próbuje zrobić miejsce dla nowego obiektu(o ile GC wcześniej się o to sam nie postarał), jeśli się to nie uda zostanie rzucony wyjątek OutOfMemoryException i JVM zakończy swoje działanie.GC(Garbage collection)
Jest mechanizmem do odzyskiwania pamięci która nie jest już wykorzystywana. GC jest uruchamiany automatycznie, i posiada kilka zaimplementowanych algorytmów odśmiecania, które zostaną opisane poniżej.MODEL PAMIĘCI
![]() |
Rys. 1 Szczegółowy model pamięci. |
Poniżej po krótce co przechowują główne bloki pamięci:
Method area structure:
- klasy, kod metod oraz konstruktory
- zmienne które są potrzebne w runtime
- Program counter - adres instrukcji które są obecnie wykonywane, jeśli metoda jest natywna to brak wartości
- Stack thread - zmienne lokalne, referencje
- Stack native - wspiera natywne metody
Gdy obiekt zostaje stworzony poprzez 'new' trafia do Eden space. Jeśli przetrwa najazd GC, zostanie promowany do Survivor Space, natomiast jeśli tutaj utrzyma się wystarczająco długo zostanie nominowany do Tenured Generation(na powyższym obrazku opisana jako Old Generation)
Prosty kawałek kodu, na przykładzie którego pokażę co gdzie wyląduje.
- public class Main {
- public static void main(String[] args) {
- try {
- String hello = "Hello ";
- StringBuilder stringBuilder = new StringBuilder(hello);
- stringBuilder.append(" DISCOVET IT");
- System.out.println(stringBuilder.toString());
- }catch (Exception e){
- System.out.println("Unexpected error");
- }
- }
- }
![]() |
Rys. 2 Tabela, która pokazuje umiejscowienie zmiennych, obiektów oraz klas |
UWAGA
Permanent Generation, inaczej PermGen, nie podlega pod GC. Zawiera niezmienne obiekty, definicje klas oraz stałe typu String. W związku z tym należy uważać gdy dynamicznie zaczniemy wytwarzać klasy lub stałe.PermGen w Java 8 ma zostać zastąpiony na rzecz Metaspace, ale ciekawskich odsyłam do sieci:)
BŁĘDY KTÓRYCH NIE CHCESZ POPRAWIAĆ
Memory Leaks / OutOfMemory
Zachodzi wtedy gdy przechowujemy referencję do obiektów których już nie używamy, uniemożliwiając tym samym usunięcie ich przez GC.Rodzaje OutOfMemoryError, które najczęściej występują:
- Exception in thread "main" java.lang.OutOfMemoryError: Java heap space(pamięć dla obiektu nie może być zarezerwowana)
- Exception in thread "main" java.lang.OutOfMemoryError: PermGen space(klasa i metody nie mogą zostać załadowane do PermGen)
- Exception in thread "main" java.lang.OutOfMemoryError: Requested array size exceeds VM limit(zachodzi gdy rozmiar tablicy jest większy nić rozmiar kopca)
- Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded(The concurrent collector will throw an OutOfMemoryError if too much time is being spent in garbage collection: if more than 98% of the total time is spent in garbage collection and less than 2% of the heap is recovered, an OutOfMemoryError will be thrown.) - fragment z dokumentacji, nie tłumaczyłem bo niektóre rzeczy lepiej wyglądają w oryginale :)
Poniżej prosty przykład kodu i screen z programu VisualVM obrazujący jak w tym przypadku pracuje GC. JVM została uruchomiona z parametrem -Xmx10M.
- public class Main {
- public static void main(String[] args) throws IOException {
- System.out.println("HELLO MEMORY LEAK");
-
- while (true){
- for (int i = 0; i < 100000; i++) {
- new MySimpleObject("GIVE ME MEMORY LEAK "+i);
- }
- }
- }
- }
Niestety powyższy kod nie spowoduje wycieku pamięci, ponieważ GC bardzo sprawnie wykonuje swoją pracę, co jest widoczne na poniższym rysunku.- public class Main {
- public static void main(String[] args) throws IOException {
- System.out.println("HELLO MEMORY LEAK");
- while (true){
- for (int i = 0; i < 100000; i++) {
- new MySimpleObject("GIVE ME MEMORY LEAK "+i);
- }
- }
- }
- }
|
A teraz szybkie zadanie, popraw powyższy kod w taki sposób, aby na konsoli zobaczyć poniższy wynik:
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
StackOverflowError
Gdy jest wywoływana pewna metoda, jej zmienne lokalne wędrują na samą górę stosu. Niestety obszar stosu jest ograniczony. W przypadku gdy metoda rekurencyjna, zbyt wiele razy zostanie wywołana, może dojść do sytuacji gdy nie będzie miejsca dla jej zmiennych. Wtedy zobaczymy błąd o którym mowa. Poznałeś przyczynę błędu, więc za pewne wiesz jak stworzyć taki błąd? Pomyśl.... myśl... myśl... myśl... myśl... myśl... myśl... myśl... myśl. Jeśli ci się nie udało to podpowiem, po prostu wywołaj metodę rekurencyjną i nie przerywaj jej działania:)Algorytmy GC
Reference Counting
Najprostsza metoda odśmiecania. Alokowany obiekty posiada dodatkowe pole, które przechowuje liczbę odwołań. Na starcie pole to ustawiane jest na 1. Podczas tworzenia obiektów lub ich usuwania, licznik jest zwiększany, lub zmniejszany. Wyzerowanie licznika oznacza, iż przydzielona pamięć może zostać zwolniona.Concurrent GC
Inaczej mark-and-sweep, podstawowy algorytm GC. Każdy obiekt otrzymuje tzw. markbit mówiący czy obiekt jest potrzebny czy nie(0, 1). Pamięć nie jest odzyskiwana gdy stwierdzi się,
że obiekt jest już niepotrzebny, lecz gdy zostanie przekroczona pamięć pewnego progu. Program jest wtedy zatrzymywany i uruchamiana jest faza odśmiecania. Wyróżniamy dwie fazy:
- Mark - GC oznacza używane obiekty(markbit = 1)
- Sweep - GC usuwa obiekty, których markbit wynosi 0 oraz rejestruje nowe, czyli te które nie są już potrzebne.
Copying Collector
Heap jest dzielony na dwa równe obszary. Pierwszy zawiera aktywne dane, drugi nieaktywne. Gdy aktywna połowa staje się pełna, wątek programu zostaje zatrzymany, żywe obiekty zostają przekopiowane do nieaktywnej części pamięci, a następnie aktywna część pamięci zostaje wyczyszczona. W kolejnej iteracji działanie jest takie samo, tylko nieaktywna część pamięci stała się aktywną, a aktywna nieaktywną. (mam nadzieję, że nie zaciemniłem :)Mark-Compact Collector
Wszystkie wątki aplikacji zostają zatrzymane. Wyróżniamy dwie fazy:- Mark - zaczynając od korzenia, każda referencja jest odwiedzana i znakowana
- Compact - cały kopiec jest skanowany, wszystkie nieoznaczone referencje są zbierane i ustawiane na sam szczyt kopca z flagą która mówi że powinny zostać usunięte
Generational Collector
Najnowszy algorytm odśmiecania, występuje od Java 6, w wersji Java 7 został zoptymalizowany. Heap jest podzielony na tzw. "generations". Obiekty są tworzone w "young generations". Gdy pamięć jest potrzebna, następuje proces nominacji obiektów do wyższych stref lub jeśli są niepotrzebne, są gromadzone przez GC. "Old generation" używa algorytmu Mark-Compact do odśmiecania.(Gdy znajdę chwilę, podlinkuje tutaj artykuł w którym porównam wszystkie algorytmy)
JVM – ustawienia pamięci
-Xmx1024M (Maximum heap), parametr na starcie nie ustawi od razu takiej ilości pamięci, stanie się to dopiero wtedy gdy zajdzie potrzeba alokacji pamięci, która wykracza poza obecne zasoby, wcześniej zanim to nastąpi zostanie uruchomiony GC, który będzie próbował zrobić porządek.
-Xms512M (pamięć jaką zostanie zainicjalizowany heap)
-XX:MaxPermSize=1024M
-XX:PermSize=512M
-XX:+HeapDumpOnOutOfMemoryError (stan sterty przed wystąpieniem błędu zostanie zrzucony do pliku)
-XX:HeapDumpPath=C:\sciezka_do_pliku
-XX:ThreadStackSize=512M (nie należy go używać, jeśli napotkamy błąd StackOverflowExceptions, należy poprawić kod)
W jaki sposób ustawić powyższe parametry? Pokażę na przykładzie środowiska Intellij, natomiast jeśli komuś to nie wystarczy to odsyłam do google z frazą "
![]() | ||||||||
Rys. 4 ustawienia parametrów JVM w środowisku Intellij 13 |
W kolejnym artykule, postaram się opisać, jak powinno wyglądać prawidłowe odśmiecanie kodu :)
Źródła
- Java Performance, Binu John, Charlie Hun
- http://www.oracle.com/technetwork/java/javase/gc-tuning-6-140523.html
- http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html?cid=6628&ssid=0
- https://www.youtube.com/watch?v=DoJr5QQYsl8
- Internet :)
Brak komentarzy:
Prześlij komentarz