Optymalizacja wykorzystania pamięci aplikacji Java w chmurze PaaS
Automatyzacja zarządzania pamięcią metodą na redukcję kosztów utrzymania środowisk w chmurze UniCloud.
Odśmiecanie pamięci używanej przez aplikacje Java z wykorzystaniem zautomatyzowanych narzędzi i mechanizmów jest sposobem ograniczenia kosztów utrzymania środowisk na platformie PaaS, co w połączeniu z mechanizmami autoskalowania zarówno pionowego, jak i poziomego, pozwala na znaczną redukcję koszów bieżącej eksploatacji środowisk.
Jedną z metod automatycznego zarządzania dynamicznie przydzieloną pamięcią jest jej odśmiecanie, czyli automatyczna dealokacja (Garbage collection), w której za proces jej zwalniania odpowiada programowy nadzorca (garbage collector).
W procesie obserwowana jest pamięć sterty, identyfikowane są obiekty będące w użyciu oraz usuwane obiekty nieużywane. Obiekt będący w użyciu lub obiekt referencyjny to taki, do którego jakaś część programu nadal utrzymuje wskaźnik. Obiekt nieużywany lub obiekt bez odniesienia nie jest już przywoływany przez żadną część programu. Tak więc pamięć używana przez obiekt bez odniesienia może zostać odzyskana. W aplikacjach napisanych w Java proces dealokacji pamięci jest obsługiwany przez zautomatyzowany garbage collector. Odbywa się to w kilku krokach:
1. Oznaczanie – następuje określenie, które części pamięci są w użyciu, a które nie.
2. Normalne usuwanie – usunięcie obiektów bez odwołań, z pozostawieniem odwołań do obiektów i wskaźników do wolnej przestrzeni.
2a. Usuwanie z kompaktowaniem – oprócz usuwania następuje skompresowanie pozostałych obiektów [1].
Podczas projektowania rozwiązania opartego o architekturę mikrousług, stosuje się niewielkie elementy, które zapewniają więcej korzyści w porównaniu do podejścia opartego o duże rozwiązania monolityczne. W tym przypadku rozmiar ma znaczenie. Najnowsza wersja Jigsaw Java pomaga dekomponować starsze aplikacje oraz tworzyć nowe, projektowane z myślą o rozwiązaniach chmurowych. Takie podejście zmniejsza przestrzeń dyskową, czas budowy i uruchomienia, nie pomaga jednak w zarządzaniu pamięcią RAM. Jak wiadomo, Java w wielu przypadkach zużywa dużą ilość pamięci. Jednocześnie Java stała się znacznie bardziej elastyczna pod względem wykorzystania pamięci i zapewnia funkcje spełniające wymagania mikrousług. Dzięki temu Java staje się bardziej elastyczna, szybciej się skaluje, obniża całkowity koszt posiadania (TCO).
W chmurze UniCloud istnieją trzy opcje skalowania środowisk: pionowe, poziome i mieszane. Aby zoptymalizować wydajność, najpierw należy ustawić skalowanie pionowe. Następnie, gdy projekt będzie się rozwijać w poziomie, wstępnie skonfigurowane zużycie zasobów
w pojedynczym kontenerze zostanie zreplikowane do każdej instancji, a wydajność środowiska wzrośnie proporcjonalnie do ilości instancji.
Poprawnie skonfigurowane skalowanie pionowe działa idealnie zarówno dla mikroserwisów, jak i monolitów, optymalizując wykorzystanie pamięci i procesora, zgodnie z bieżącym obciążeniem wewnątrz kontenerów. Wybór odpowiedniego dealokatora jest sprawą fundamentalną, a jego ustawienia mogą mieć wpływ na cały projekt.
W OpenJDK istnieje pięć szeroko używanych rozwiązań do sprzątania pamięci:
• G1
• Parallel
• ConcMarkSweep (CMS)
• Serial
• Shenandoah
Do testowania można użyć przykładowej aplikacji Java, która pomaga śledzić wyniki skalowania pionowego JVM: https://github.com/jelastic/java-vertical-scaling-test, cały proces i rodzaje kolektorów opisał Rusłan Synytsky w [2].
Przy każdym projekcie brane są pod uwagę dwa główne aspekty związane z hostingiem aplikacji: wydajność i cena. Podstawowe pytanie, stawiane przy dobieraniu rozwiązania do potrzeb brzmi: jak ograniczyć koszty bez wpływu na wydajność aplikacji. Przyjrzyjmy się, w jaki sposób zautomatyzować zarządzanie pamięcią dla aplikacji Java hostowanych w UniCloud. Wykorzystamy do tego technologię Java Garbage Collection.
Wartości domyślne w UniCloud (można je zmienić) są następujące[3]:
• ParNew dla serwerów z zasobami poniżej 8GB
• G1 dla serwerów z zasobami powyżej 8GB RAM.
W tym przypadku nie ma znaczenia, ile zasobów jest aktualnie używanych. Garbage Collector uwzględnia zadeklarowany limit skalowania – maksymalną ilość zasobów ustalonych dla każdego serwera osobno.
ParNew Garbage Collector
ParNew jest to wielowątkowy Garbage Collector typu „stop-the-world”. Gromadzi nowe generacje obiektów. Ponieważ zazwyczaj są one niewielkich rozmiarów, ParNew obsługuje jedynie serwery z ograniczeniami poniżej 8GB. Dzięki temu jest bardzo szybki i nie ma wielkiego wpływu na działanie aplikacji. Ponadto ParNew kompaktuje niewykorzystaną pamięć RAM, co umożliwia wsparcie dla automatycznego skalowania poziomego, jednej
z czołowych funkcjonalności dostępnych w UniCloud.
Garbage-First Garbage Collector
Garbage-First (G1) GC jest to Garbage Coollector w stylu serwerowym, zaprojektowanym dla wieloprocesorowych maszyn z dużą ilością pamięci. Stos jest dzielony na regiony
o stałej wielkości, a G1 śledzi dane w regionach w trybie ciągłym. Jeśli pojawi się konieczność uruchomienia procesu Garbage Collection, najpierw sprzątane są regiony z mniej świeżymi danymi. G1 koncentruje się na aplikacjach, które wymagają dużych stosów z ograniczoną latencją GC. Głęboka analiza wykazała, że 8GB jest wielkością wystarczającą do używania G1.
Aby nieużywaną pamięć RAM zwolnić z JVM do OS, wraz z powyższymi ustawieniami używany jest specjalizowany agent GC.
Dostosowywanie ustawień Garbage Collector w chmurze UniCloud
Jeśli uważasz, że domyślne parametry GC powinny zostać skorygowane, możesz je dostosować zgodnie z wymaganiami aplikacji. Zalecamy jednak szczególną ostrożność
i dokonywanie zmian jedynie wtedy, gdy ich wpływ na wydajność aplikacji jest w pełni znany i zrozumiany.
Parametry GC dla aplikacji można zmienić poprzez:
• edycję pliku variables.conf dla serwera Tomcat, TomEE i Jetty
• edycję JVM option w panelu administracyjnym serwera GlassFish[3]
Parametry GC ustawia się używając składni:
-XX:+UseParNewGC
-XX:+UseG1GC
-XX:+UseConcMarkSweepGC
-XX:+UseSerialGC
Aktualizacje JDK wprowadziły kolejne optymalizacje, tym razem pod kątem Dockera [4], Nowa funkcja JVM (-XX:+UseCGroupMemoryLimitForHeap) automatycznie ustawia Xmx dla procesu Javy zgodnie z limitem pamięci określonym w cgroup.
Zmiany w użyciu pamięci RAM podczas optymalizacji działania aplikacji Java na platformie UniCloud, można prześledzić na wspomnianej wcześniej stronie Java Code Geeks [2]
Info:
[1] Java Garbage Collection Basics:
http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html
[2] Synytsky, R. Minimize Java Memory Usage with the Right Garbage Collector
https://www.javacodegeeks.com/2017/11/minimize-java-memory-usage-right-garbage-collector.html
[3] Baza Wiedzy UniCloud:
http://pomoc.unicloud.pl/unicloud/hosting-aplikacji/garbage-collector
[4] Baza Wiedzy UniCloud:
http://pomoc.unicloud.pl/unicloud/srodowiska-serwery/java-wykorzystanie-pamieci-ram-kontenerach-5-porad-oszczednosc