Konteneryzowanie aplikacji Java

Ten artykuł zawiera omówienie zalecanych strategii i ustawień konteneryzowania aplikacji Java.

Podczas konteneryzowania aplikacji Java należy dokładnie zastanowić się, ile czasu procesora CPU będzie dostępny w kontenerze. Następnie zastanów się, ile pamięci będzie dostępna zarówno pod względem całkowitej ilości pamięci, jak i rozmiar sterty maszyny wirtualnej Java (JVM). W środowiskach konteneryzowanych aplikacje mogą mieć dostęp do wszystkich procesorów i dlatego mogą równolegle uruchamiać wiele wątków. Często jednak kontenery mają zastosowany limit przydziału procesora CPU, który może ograniczać dostęp do procesorów CPU.

Maszyny JVM mają heurystyki umożliwiające określenie liczby "dostępnych procesorów" na podstawie limitu przydziału procesora CPU, co może znacząco wpłynąć na wydajność aplikacji Java. Pamięć przydzielona do samego kontenera i rozmiar obszaru sterty dla maszyny JVM są tak ważne jak procesory. Te czynniki określają zachowanie modułu odśmiecania pamięci (GC) i ogólną wydajność systemu.

Konteneryzowanie nowej aplikacji

Podczas konteneryzacji obciążenia Java dla nowej aplikacji należy wziąć pod uwagę dwie kwestie podczas myślenia o pamięci:

  • Pamięć przydzielona do samego kontenera.
  • Ilość pamięci dostępnej dla procesu Java.

Omówienie domyślnej anatomii maszyny wirtualnej JVM

Aplikacje wymagają punktu początkowego i ustawień. Maszyny JVM mają domyślną klawiaturę ze wstępnie zdefiniowanymi wartościami, które są oparte na liczbie dostępnych procesorów i ilości pamięci w systemie. Wartości domyślne wyświetlane w poniższych tabelach są używane podczas uruchamiania maszyny wirtualnej JVM bez określonych flag uruchamiania lub parametrów.

W poniższej tabeli przedstawiono domyślną GC używaną dla dostępnych zasobów:

Dostępne zasoby Domyślna GC
Dowolna liczba procesorów
Do 1791 MB pamięci
SerialGC
Procesory 2+
1792 MB lub więcej pamięci
G1GC

W poniższej tabeli przedstawiono domyślny maksymalny rozmiar sterty w zależności od ilości pamięci dostępnej w środowisku, w którym działa maszyna wirtualna JVM:

Dostępna pamięć Domyślny maksymalny rozmiar sterty
Do 256 MB 50% dostępnej pamięci
Od 256 MB do 512 MB ~127 MB
Więcej niż 512 MB 25% dostępnej pamięci

Domyślny początkowy rozmiar sterty to 1/64 dostępnej pamięci.

Te wartości są prawidłowe dla zestawu OpenJDK 11 lub nowszego oraz dla większości dystrybucji, w tym microsoft build of OpenJDK, Azul Zulu, Eclipse Temurin, Oracle OpenJDK i innych.

Określanie pamięci kontenera

Wybierz ilość pamięci kontenera, która będzie najlepiej obsługiwać obciążenie pracy, w zależności od potrzeb aplikacji i jej charakterystycznych wzorców użycia. Jeśli na przykład aplikacja tworzy duże grafy obiektów, prawdopodobnie potrzebujesz więcej pamięci niż w przypadku aplikacji z wieloma małymi grafami obiektów.

Napiwek

Jeśli nie wiesz, ile pamięci należy przydzielić, dobrym punktem wyjścia jest 4 GB.

Określanie pamięci stert JVM

Podczas przydzielania pamięci stertowej JVM należy pamiętać, że JVM potrzebuje więcej pamięci niż tylko to, co jest używane na stercie JVM. Po ustawieniu maksymalnej pamięci stertowej JVM nigdy nie powinna być równa ilości pamięci kontenera, ponieważ spowoduje to awarie kontenera poza pamięcią (OOM, Container Out of Memory) i kontenera.

Napiwek

Przydziel 75% pamięci kontenera na stertę JVM.

W zestawie OpenJDK 11 lub nowszym można ustawić rozmiar sterty JVM na następujące sposoby:

opis Flaga Przykłady
Stała wartość -Xmx -Xmx4g
Wartość dynamiczna -XX:MaxRAMPercentage -XX:MaxRAMPercentage=75

Minimalny/początkowy rozmiar sterty

Jeśli środowisko ma zagwarantowaną określoną ilość pamięci zarezerwowanej dla wystąpienia JVM, takiego jak w kontenerze, należy ustawić minimalny rozmiar sterty — lub początkowy rozmiar sterty — na taki sam rozmiar, jak maksymalny rozmiar sterty. To ustawienie wskazuje maszynę wirtualną JVM, że nie powinno wykonywać zadania zwalniania pamięci do systemu operacyjnego.

Aby ustawić minimalny rozmiar sterty, użyj wartości -Xms bezwzględnych lub -XX:InitialRAMPercentage wartości procentowych.

Ważne

Flaga -XX:MinRAMPercentage, pomimo tego, co sugeruje nazwa, jest używana do ustawiania domyślnej maksymalnej wartości procentowej pamięci RAM dla systemów z maksymalnie 256 MB pamięci RAM dostępnej w systemie.

Chart showing the default heap size on OpenJDK 17.

Określanie, którego GC użyć

Wcześniej określono ilość pamięci stertowej JVM na początek. Następnym krokiem jest wybranie GC. Maksymalna ilość pamięci stertowej JVM, którą masz, jest często czynnikiem podczas wybierania GC. W poniższej tabeli opisano cechy każdej GC.

Czynników SerialGC ParallelGC G1GC ZGC ShenandoahGC
Liczba rdzeni 1 2 2 2 2
Multi-threaded Nie. Tak Tak Tak Tak
Rozmiar sterty java <4 Gb/s <4 Gb/s >4 Gb/s >4 Gb/s >4 Gb/s
Wstrzymanie Tak Tak Tak Tak (<1 ms) Tak (<10 ms)
Obciążenie Minimalny Minimalny Średnio Średnio Średnio
Efekt opóźnienia końcowego Maksimum Maksimum Maksimum Minimum Średnio
Wersja zestawu JDK wszystkie wszystkie JDK 8+ JDK 17+ JDK 11+
Optymalne zastosowanie Mała sterta z pojedynczym rdzeniem Wielordzeniowe małe sterty lub obciążenia wsadowe z dowolnym rozmiarem sterty Odpowiadanie w średnich i dużych stertach (interakcja z żądaniem/bazą danych) Odpowiadanie w średnich i dużych stertach (interakcja z żądaniem/bazą danych) Odpowiadanie w średnich i dużych stertach (interakcja z żądaniem/bazą danych)

Napiwek

W przypadku większości aplikacji mikrousług ogólnego przeznaczenia zacznij od równoległego GC.

Określanie, ile rdzeni procesora CPU jest potrzebnych

W przypadku dowolnego GC innego niż SerialGC zalecamy co najmniej dwa rdzenie procesorów wirtualnych — lub co najmniej 2000mcpu_limit na platformie Kubernetes. Nie zalecamy wybierania mniej niż 1 rdzenia procesorów wirtualnych w środowiskach konteneryzowanych.

Napiwek

Jeśli nie wiesz, ile rdzeni należy zacząć od, dobrym wyborem jest 2 rdzenie procesorów wirtualnych.

Wybierz punkt początkowy

Zalecamy rozpoczęcie od dwóch replik lub wystąpień w środowiskach orkiestracji kontenerów, takich jak Kubernetes, OpenShift, Azure Spring Apps, Azure Container Apps i aplikacja systemu Azure Service. Poniższa tabela zawiera podsumowanie zalecanych punktów początkowych dla konteneryzacji nowej aplikacji Java.

Rdzenie procesorów wirtualnych Pamięć kontenera Rozmiar sterty JVM GC Repliki
2 4 GB 75% ParallelGC 2

Parametry maszyny JVM do użycia to: -XX:+UseParallelGC -XX:MaxRAMPercentage=75

Konteneryzowanie istniejącej (lokalnej) aplikacji

Jeśli aplikacja jest już uruchomiona lokalnie lub na maszynie wirtualnej w chmurze, zalecamy rozpoczęcie od:

  • Ta sama ilość pamięci, do którego aplikacja ma obecnie dostęp.
  • Ta sama liczba procesorów CPU (rdzeni procesorów wirtualnych) jest obecnie dostępna dla aplikacji.
  • Te same parametry JVM, których obecnie używasz.

Jeśli kombinacja rdzeni procesorów wirtualnych i/lub pamięci kontenera jest niedostępna, wybierz najbliższą, zaokrąglając rdzenie procesorów wirtualnych i pamięć kontenera.

Następne kroki

Teraz, po zapoznaniu się z ogólnymi zaleceniami dotyczącymi konteneryzowania aplikacji Java, przejdź do następującego artykułu, aby ustanowić punkt odniesienia konteneryzacji: