Udostępnij za pośrednictwem


Zarządzanie pamięcią w języku Java

Uwaga

Plany Basic, Standardi Enterprise weszły w okres wycofywania 17 marca 2025 r. Aby uzyskać więcej informacji, zobacz ogłoszenie o wycofaniu usługi Azure Spring Apps.

Plan dotyczący zużycia standardowego oraz dedykowany plan zostały wycofane 30 września 2024 r., z całkowitym zamknięciem do końca marca 2025 r. Aby uzyskać więcej informacji, zobacz Migrowanie standardowego i dedykowanego planu wykorzystania aplikacji Azure Spring Apps do usługi Azure Container Apps.

Ten artykuł dotyczy:✅ Wersja Podstawowa/Standardowa ❎ Wersja Enterprise

W tym artykule opisano różne pojęcia związane z zarządzaniem pamięcią w języku Java, które ułatwiają zrozumienie zachowania aplikacji Java hostowanych w usłudze Azure Spring Apps.

Model pamięci Języka Java

Pamięć aplikacji Java ma kilka części i istnieją różne sposoby dzielenia części. W tym artykule omówiono pamięć języka Java podzieloną na pamięć stertową, pamięć niezwiązaną z stertą i pamięć bezpośrednią.

Pamięć stert

Pamięć stertowa przechowuje wszystkie wystąpienia klas i tablice. Każda maszyna wirtualna Java (JVM) ma tylko jeden obszar sterty, który jest współdzielony między wątkami.

Spring Boot Actuator może obserwować wartość pamięci sterty. Spring Boot Actuator przyjmuje wartość kupy pamięci w ramach elementu jvm.memory.used/committed/max. Aby uzyskać więcej informacji, zobacz sekcję jvm.memory.used/committed/max w temacie Narzędzia do rozwiązywania problemów z pamięcią.

Pamięć sterta jest podzielona na młode pokolenie i stare pokolenie. Te terminy są opisane na poniższej liście wraz z powiązanymi terminami.

  • Generacja młoda: wszystkie nowe obiekty są przydzielane i starzeją się w generacji młodej.

    • Przestrzeń Eden: nowe obiekty są przydzielane w przestrzeni Eden.
    • Przestrzeń ocalałych: obiekty zostaną przeniesione z Eden do przestrzeni ocalałych po przetrwaniu jednego cyklu odzyskiwania pamięci. Przestrzeń ocalałych można podzielić na dwie części: s1 i s2.
  • Stare pokolenie: nazywane również przestrzenią trwałą. Obiekty, które pozostały w obszarach przetrwałych przez długi czas, zostaną przeniesione do starej generacji.

Przed językiem Java 8 kolejna sekcja o nazwie trwałe generowanie była również częścią sterta. Począwszy od języka Java 8, trwałe generowanie zostało zastąpione przez przestrzeń metadanych w pamięci niezwiązanej z stertą.

Pamięć niezwiązana z stertą

Pamięć poza stertą jest podzielona na następujące części:

  • Część pamięci innej niż sterta, która zastąpiła stałą generację (lub permGen) począwszy od Java 8. Aktuator Spring Boot obserwuje tę sekcję i przyjmuje ją jako część jvm.memory.used/committed/max. Innymi słowy, jvm.memory.used/committed/max jest sumą pamięci sterty i dawnej części permGen pamięci poza stertą. Poprzednie trwałe pokolenie składa się z następujących części:

    • Metaspace, która przechowuje definicje klas ładowane przez moduły ładujących klasy.
    • Skompresowana przestrzeń klas dla skompresowanych wskaźników klasy.
    • Pamięć podręczna kodu, która przechowuje kod natywny skompilowany przez JIT.
  • Inne rodzaje pamięci, takie jak stos wątków, które nie są monitorowane przez Spring Boot Actuator.

Pamięć bezpośrednia

Pamięć bezpośrednia to pamięć natywna przydzielona przez java.nio.DirectByteBuffer, która jest używana w bibliotekach stron trzecich, takich jak nio i gzip.

Spring Boot Actuator nie obserwuje wartości pamięci bezpośredniej.

Na poniższym diagramie przedstawiono podsumowanie modelu pamięci Języka Java opisanego w poprzedniej sekcji.

Diagram przedstawiający model pamięci Języka Java.

Zbieranie śmieci w Java

Istnieją trzy terminy dotyczące zarządzania pamięcią w języku Java (GC): "Minor GC", "Major GC" i "Full GC". Te terminy nie są jasno zdefiniowane w specyfikacji JVM. W tym kontekście uważamy, że "Duże GC" i "Pełne GC" są równoważne.

Drobne GC występuje, gdy przestrzeń Eden jest pełna. Usuwa wszystkie martwe obiekty w młodym pokoleniu i przenosi żywe obiekty z przestrzeni Eden do przestrzeni s1 ocalałych lub z s1 do s2.

Pełne GC lub główne GC wykonuje odzyskiwanie pamięci w całej stercie. Pełny GC może także zbierać części, takie jak metaprzestrzeń i pamięć bezpośrednia, które mogą być czyszczone tylko przez pełny GC.

Maksymalny rozmiar sterty ma wpływ na częstotliwość drobnych GC i pełne GC. Maksymalna przestrzeń metadanych i maksymalny rozmiar pamięci bezpośredniej mają wpływ na pełną GC.

Po ustawieniu niższej wartości maksymalnego rozmiaru sterty, odzyskiwanie pamięci odbywa się częściej, co trochę spowalnia działanie aplikacji, ale lepiej ogranicza użycie pamięci. Po ustawieniu maksymalnego rozmiaru sterty na wyższą wartość, procesy zbierania śmieci odbywają się rzadziej, co może zwiększać ryzyko wystąpienia błędu braku pamięci (OOM). Aby uzyskać więcej informacji, zobacz sekcję Typy problemów z brakiem pamięci w temacie Problemy z ponownym uruchamianiem aplikacji powodowane przez problemy z brakiem pamięci.

Przestrzeń metadanych i pamięć bezpośrednia mogą być zbierane tylko przez pełną GC. Gdy przestrzeń metadanych lub pamięć bezpośrednia jest pełna, nastąpi pełna GC.

Konfiguracje pamięci Języka Java

W poniższych sekcjach opisano ważne aspekty konfiguracji pamięci Języka Java.

Konteneryzacja języka Java

Aplikacje w usłudze Azure Spring Apps działają w środowiskach kontenerów. Aby uzyskać więcej informacji, zobacz Containerize your Java applications (Konteneryzowanie aplikacji Java).

Ważne opcje JVM

Maksymalny rozmiar każdej części pamięci można skonfigurować przy użyciu opcji JVM. Opcje JVM można ustawić przy użyciu poleceń interfejsu wiersza polecenia platformy Azure lub witryny Azure Portal. Aby uzyskać więcej informacji, zobacz sekcję Modyfikowanie konfiguracji w celu rozwiązania problemów z narzędziami do rozwiązywania problemów z pamięcią.

Poniższa lista zawiera opis opcji JVM:

  • Konfiguracja rozmiaru sterty

    • -Xms Ustawia początkowy rozmiar sterty według wartości bezwzględnej.
    • -Xmx Ustawia maksymalny rozmiar sterty według wartości bezwzględnej.
    • -XX:InitialRAMPercentage Ustawia początkowy rozmiar sterty według procentu rozmiaru sterty/rozmiaru pamięci aplikacji.
    • -XX:MaxRAMPercentage Ustawia maksymalny rozmiar sterty według procentu rozmiaru sterty/rozmiaru pamięci aplikacji.
  • Konfiguracja rozmiaru pamięci bezpośredniej

    • -XX:MaxDirectMemorySize Ustawia maksymalny rozmiar pamięci bezpośredniej według wartości bezwzględnej. Aby uzyskać więcej informacji, zobacz MaxDirectMemorySize w dokumentacji oracle.
  • Konfiguracja rozmiaru przestrzeni metadanych

    • -XX:MaxMetaspaceSize Ustawia maksymalny rozmiar przestrzeni metadanych według wartości bezwzględnej.

Domyślny maksymalny rozmiar pamięci

W poniższych sekcjach opisano sposób ustawiania domyślnych maksymalnych rozmiarów pamięci.

Domyślny maksymalny rozmiar sterty

Azure Spring Apps ustawia domyślny maksymalny rozmiar pamięci sterty na około 50%-80% pamięci przydzielonej aplikacjom Java. W szczególności usługa Azure Spring Apps używa następujących ustawień:

  • Jeśli pamięć < aplikacji wynosi 1 GB, domyślny maksymalny rozmiar sterty będzie wynosić 50% pamięci aplikacji.
  • Jeśli 1 GB <= pamięć aplikacji <= 2 GB, domyślny maksymalny rozmiar sterty wynosić będzie 60% pamięci aplikacji.
  • Jeżeli pamięć aplikacji wynosi od 2 GB do 3 GB, domyślnie maksymalny rozmiar sterty wyniesie 70% pamięci aplikacji.
  • Jeśli pamięć aplikacji wynosi 3 GB <, domyślny maksymalny rozmiar sterty będzie wynosić 80% tej pamięci.

Domyślny maksymalny rozmiar pamięci bezpośredniej

Jeśli maksymalny rozmiar pamięci bezpośredniej nie jest ustawiany przy użyciu opcji JVM, funkcja JVM automatycznie ustawia maksymalny rozmiar pamięci bezpośredniej na wartość zwracaną przez runtime.getRuntime.maxMemory(). Ta wartość jest w przybliżeniu równa maksymalnemu rozmiarowi sterty pamięci. Aby uzyskać więcej informacji, zobacz plik JDK 8 VM.java.

Układ użycia pamięci

Rozmiar sterty jest zależny od przepływności. Zasadniczo, podczas konfigurowania można zachować domyślny maksymalny rozmiar sterty, który pozostawia rozsądną ilość pamięci dla innych części.

Rozmiar przestrzeni metadanych zależy od złożoności kodu, takiej jak liczba klas.

Bezpośredni rozmiar pamięci zależy od przepływności i korzystania z bibliotek innych firm, takich jak nio i gzip.

Na poniższej liście opisano typowy przykład układu pamięci dla aplikacji 2 GB. Aby skonfigurować ustawienia rozmiaru pamięci, możesz zapoznać się z tą listą.

  • Łączna ilość pamięci (2048M)
  • Pamięć stertowa: Xmx wynosi 1433,6 mln (70% całkowitej pamięci). Wartość referencyjna dziennego użycia pamięci wynosi 1200 mln.
    • Młode pokolenie
      • Przestrzeń ocalałych (S0, S1)
      • Przestrzeń Eden
    • Stare pokolenie
  • Pamięć niezwiązana z stertą
    • Obserwowana część (obserwowana przez Spring Boot Actuator)
      • Metaspace: wartość referencyjna dziennego użycia to 50M-256M
      • Pamięć podręczna kodu
      • Skompresowana przestrzeń klasy
    • Niezaobserwowana część (niezauważona przez Spring Boot Actuator): wartość referencyjna dziennego użycia wynosi 150M-250M.
      • Stos wątków
      • GC, symbol wewnętrzny i inne elementy
  • Pamięć bezpośrednia: wartość referencyjna dziennego użycia to 10M-200M.

Na poniższym diagramie przedstawiono te same informacje. Liczby w kolorze szarym to wartości referencyjne dziennego użycia pamięci.

Diagram typowego układu pamięci dla aplikacji 2 GB.

Ogólnie rzecz biorąc, podczas konfigurowania maksymalnych rozmiarów pamięci należy wziąć pod uwagę użycie każdej części w pamięci, a suma wszystkich maksymalnych rozmiarów nie powinna przekraczać całkowitej dostępnej pamięci.

Java OOM

OOM oznacza, że aplikacji zabrakło pamięci. Istnieją dwie różne pojęcia: kontener OOM i OOM JVM. Aby uzyskać więcej informacji, zobacz Problemy z ponownym uruchamianiem aplikacji spowodowane problemami braku pamięci.

Zobacz też