Java Developer? Przejdź na wyższy poziom wiedzy 🔥💪  Sprawdź

Team Leader? Podnieś efektywność swojego zespołu 👌 Sprawdź

Ważne parametry JVM, o których powinieneś wiedzieć przed wyjściem na produkcję

utworzone przez 27 września 2020Java, Tip and Tricks

Zbieramy wymagania, kodujemy, refaktorujemy, testujemy i puszczamy nasz nowy greenfieldowy projekt na produkcję 🚀. Dziś stworzyć działający mikroseriws jest bardzo łatwo, na co pozwala chociażby Sping Boot, Micronaut lub Quarkus.

Podczas przygotowywania aplikacji przed wyjściem na produkcję dobrze jest zwrócić uwagę na to, w jaki sposób jest uruchamiana:

  • gdzie i jak loguje,
  • jakie metryki eksponuje i gdzie je raportuje,
  • jakie javaopts włączamy.

Tym ostatnim poświęcam dzisiejszy wpis na blogu. JVM opts owiane są nutą tajemniczości i kojarzą się raczej z tuneowaniem GC (i bynajmniej do tego nie zachęcam w tym wpisie), ale kilka z nich warto znać przed wyjściem na produkcję.

Z tego artykułu dowiesz się o opcjach:

  • -Xms i -Xmx oraz pamięcią w kontekście kontenerów Dockerowych
  • -XX:+HeapDumpOnOutOfMemoryError
  • -Duser.timezone
  • -Djava.security.egd
  • -server
  • -Xloggc

-Xms i -Xmx

Xms i Xmx odpowiadają za kontrolę zużycia pamięci. Obiekty w Java są tworzone na stercie zarządzanej przez JVM poprzez alokowanie obiektów, a gdy są już nieużywane, ich usuwanie za pomocą Garbage Collector.

Więcej o pamięci i GC dowiesz się z artykułów:

Xmx to maksymalny rozmiar zaalokowanego obszaru pamięci w systemie operacyjnym, którego aplikacja nie przekroczy. Gdy pamięć się skończy, próba alokacji nowego obiektu zakończy się wyjątkiem typu Error: java.lang.OutOfMemoryError. Xmx daje Ci to kontrolę nad procesami, aby nie zaalokowały zbyt dużo pamięci i nie zagłodziły innych procesów. Przykładowe użycia:

-Xmx1G, -Xmx512M

Xms to minimalna ilość pamięci alokowana na start JVM. Można by zapytać – po co, skoro pamięc jest alokowana w miarę potrzeb? Otóż alokowanie pamięci zajmuje czas oraz powoduje co jakiś czas uruchamianie Garbage Collector.

Jeżeli Twoja aplikacja na start ładuje pewne dane do pamięci, na przykład dane do Cache, bazując na empirycznych obserwacjach i oszacowaniu ile jest danych, można przewidzieć ile pamięci aplikacja potrzebuje „na start”. Ustawiając Xms GC będzie wiedział, że nie należy sprzątać obiektów, a sama pamięć zostanie zaalokowana z góry, co z pewnością przyspieszy czas startu aplikacji*.

* Oczywiście nie wykluczam, że na start aplikacji może mieć większy wpływ inny czynnik niż GC i czas alokacji pamięci.

Maksymalna pamięć w kontenerze Docker

Coraz częściej jednak aplikacje Java są konteneryzowane za pomocą Docker’a. Przy uruchamianiu obrazu można wyspecifikować limit pamięci dla uruchomionego obrazu. Wtedy definiowanie -Xmx wydaje się być nadmiarowe, bo ustawienia są już zdefiniowane.

  • Jeżeli w projekcie używasz Java 8 (wtedy upewnij się, że masz JRE 8u131+) lub Java 9, użyj opcji: -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap.
  • Od Java 10 (JDK-8146115) maszyna wirtualna respektuje ustawienia kontenerów dockerowych – nie musisz niczego ustawiać.

-XX:+HeapDumpOnOutOfMemoryError

Pozostając w temacie pamięci. Istnieją sytuacje, kiedy aplikacji kończy się pamięć. Najczęsciej przyczyną jest:

  • Brak zwalniania zasobów metodą close()
    Takich jak wszelkiej maści połączenia (baza danych, JMS, messaging, I/O), uchwyty do plików. Coraz częściej dostęp do zasobów zapewniają biblioteki lub frameowrki (np. Spring Messaging, Spring Data), ale wciąż należy uważać korzystając z zasobów.
  • Przepełnienie buforów
    Przy okazji np. wczytywanie dużej ilości stringów, plików XML.
  • Przepełnienie kolejek
    Kolejki (te in-memory) to struktury danych, które wykorzystywane do zakolejkowania zadań przed zasobem o skończonej przepustowości. Przykładem są Executor. Gdy do pamięci akceptowane są dania pomimo tego, że nie mają szansy się zrealizować, z czasem może dojść do sytuacji, że kolejka się po prosu przepełni. Wtedy lepiej ograniczyć bufor do znanej wartości i po prostu odrzucić żądanie niż doprowadzić do katastrofy aplikacji.

Gdy już jednak dojdzie do sytuacji, że pamięć się wyczerpie i otrzymamy błąd java.lang.OutOfMemoryError, dobrze jest, zanim aplikacja całkowicie się wyłoży, zrobić zrzut pamięci (heap dump) sprzed awarii. Pozwoli nam to na diagnozę problemu po ewentualnej awarii procesu. Opcję właczamy następującymi przełącznikami:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/jakas/sciezka

Analizując pamięć nie otrzymasz jednoznacznej odpowiedzi na pytanie: alokacja którego obiektu spowodowała błąd? Ale jesteś w stanie przeanalizować stertę (np. patrząc na rozmiar obiektów, liczbę wystąpień lub skąd pochodzą) i dojść do źródła problemu.

Psst… Interesujący artykuł?

Jeżeli podoba Ci się ten artykuł i chcesz takich więcej – dołącz do newslettera. Nie ominą Cię materiały tego typu.

.

-Duser.timezone

Wprowadzone API Date and Time w Java 8 dość mocno wymusza na wyspecifikowanie strefy czasowej, gdy tworzona jest data i czas. Wciąż w Java istnieje stare API oraz wiele miejsc, gdzie nie jest jednoznaczne, jaka strefa czasowa jest używana, np.:

  • data i czas w bibliotekach logujących
  • stare API daty i czasu w Java
  • wyciąganie danych z bazy danych (standard SQL: TIMESTAMP WITH TIME ZONE) i dana typu java.sql.Timestamp lub java.time.OffsetDateTime.

Domyślną strefą czasową jest strefa systemu operacyjnego, która dostępna jest później w klasie TimeZone.getDefault().

Dobrą praktyką jest po prostu ustawić strefę czasową JVM za pomocą przełączki:

-Duser.timezone=UTC

Strefa UTC to dobry wybór z kilka powodów:

  • Wpisy w logach są logowane zawsze w jednej strefie czasowej i przy globalnych deploymentach wiadomo, kiedy wystąpiło zdarzenie (można łatwo przeliczyć do czasu lokalnego).
  • Dane w bazie danych są zapisywane w UTC, odczytywane w UTC, a zmiana strefy następuje dopiero podczas prezentacji użytkownikowi. Mamy spójny zapis daty i czasu dla różnych użytkowników i na serwerach w różnych lokalizacjach.

Żyje się prościej.

-Djava.security.egd

Gdy potrzebujemy generować pseudolosowe liczby, Java korzysta z generatora liczb. Domyślnie jest to /dev/random czyli wirtualne urządzenie w Unix, pełniące funkcję generatora losowych liczb z losowością pochodzącą ze sterowników urządzeń i innych źródeł. Brzmi jak dokładne źródło „losowości”, ale jego problemem jest to, że jest wolne.

Generowanie liczb jest potrzebne np. do celów kryptograficznych, np. generowanie certyfikatów, kluczy, seedów sesji potrzebnych do bezpiecznych połączeń. Może się okazać, że przy częstej wymianie danych sesji generowanie nowych liczb jest zbyt wolne.

Wtedy warto skorzystać z alternatywy jaką jest unblocking random, czyli: /dev/urandom. Aby ustawić źródło liczb losowych w Java należy skorzystać z przełącznika:

-Djava.security.egd=file:/dev/urandom

-server

Teraz to rzadkość, ale Java może być używana po stronie serwera oraz klienta (jako uruchamiane aplikacje). Wymagania serwera i klienta są inne: serwer chce szybko obsługiwać żadania, a klient zaalokować możliwie mało pamięci (mając na uwadze inne działające aplikacje).

Choć patrząc po statystykach niektórych przeglądarek internetowych, to mam mieszane spostrzeżenia, co jest priorytetem.

Dlatego dla aplikacji serwetowych warto skorzystać z przełączki -server, aby jawnie wyspecyfikować, jak ma się zachować JVM i Garbage Collector.

Ustawienie -server spowoduje m.in.:

  • Bardziej agresywne optymalizacje kodu (doc)
  • Więcej zaalokowanej pamięci przy domyślnych ustawieniach

-XX:+DisableExplicitGC

Włączenie opcji -XX:+DisableExplicitGC zapobiega uruchamianiu GC „z zewnątrz”, np po API JMX z konsoli lub przez inne aplikacje. Podczas podłączania się narzędziami diagnostycznymi (JConsole, Java Mission Control), mogą one wywoływać proces GC, np podczas inspekcji sterty. Przydatne, aby zagwarantować sobie deterministyczne wyniki.

-Xloggc

Gdy aktywnie nie monitorujemy aplikacji i działa ona w trybie ciągłym, w razie jakichś problemów z opóźnieniem może okazać się przydatnym logowanie pauz GC do dalszej diagnostyki. Aby włączyć logowanie zdarzeń z GC użyj przełączek:

-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -Xloggc:/jakas/sciezka/gc.log

Opcje kolejno odpowiadają za:

  • -XX:+PrintGCDetails – włącza logowanie GC z większą liczbą szczegółów w przeciwieństwie do -XX:+PrintGC.
  • -XX:+PrintGCDateStamps – loguje czasy zdarzenia. Uwaga na -XX:+PrintGCTimeStamps, który loguje czas relatywnie do uruchomienia maszyny wirtualnej – ciężko wtedy określić, kiedy GC miało miejsce.
  • -Xloggc – definiuje ścieżkę pliku, w którym umieszczane będą logi, zamiast logować wpisy na standardowe wyjście
  • -XX:+PrintTenuringDistribution – wypisuje informacje na temat generacji obiektów.

Podsumowanie

JVM to tysiące parametrów. Włączenie kluczowych z nich pozwala na spełnienie wymagań niefunkcjonalnych, takich jak stabilność systemu, szybkość działania (inicjalizacji), diagnostyka.

Dobrą inspiracją na wybór parametrów jest przeglądanie projektów Open Source i sprawdzenie jakich przełączek używają i zapoznawanie się z nimi. Uważaj natomiast, żeby zachować rozsądek i nie używać przełączek, które tuneują JVM i stawiać hipotez bez testów.

Podoba Ci się ten artykuł? Weź więcej.

Jeżeli uważasz ten materiał za wartościowy i chcesz więcej treści tego typu – nie przegap ich i otrzymuj je prosto na swoją skrzynkę. Nawiążmy kontakt.

.

Gdybyś potrzebował jeszcze więcej:

Jesteś Java Developerem?

Przejdź na wyższy poziom wiedzy 
„Droga do Seniora” 🔥💪

Jesteś Team Leaderem? Masz zespół?

Podnieś efektywność i wiedzę swojego zespołu 👌

FAQ

Czym jest -Xms i -Xmx

-Xmx służy do ustawienia maksymalnej ilości pamięci, którą zaalokuje maszyna wirtualna Java. Odpowiednio -Xms wskazuje, jak duży obszar jest przydzielany „na start”.

Czym jest -XX:+HeapDumpOnOutOfMemoryError

Włącza zrzucenie pamięci JVM do pliku w przypadku przekroczenia maksymalnego dostępnego limitu zaraz przed zakończeniem działania JVM. Pozwala na późniejszą diagnostykę po awarii.

Czym jest -XX:+PrintGCDetails i -Xloggc

Włącza logowanie zdarzeń procesu Garbage Collectora, a -Xloggc wskazuje ścieżkę do plików logu.

Obraz: Technologia zdjęcie utworzone przez pvproductions – pl.freepik.com

Dyskusja