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

Uwaga

Azure Spring Apps to nowa nazwa usługi Azure Spring Cloud. Mimo że usługa ma nową nazwę, stara nazwa będzie widoczna w niektórych miejscach przez pewien czas, ponieważ pracujemy nad aktualizowaniem zasobów, takich jak zrzuty ekranu, filmy wideo i diagramy.

Ten artykuł dotyczy: ✔️ Podstawowa/Standardowa ✔️ 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 stert, który jest współużytkowany między wątkami.

Siłownik Spring Boot może obserwować wartość pamięci sterta. Siłownik Spring Boot przyjmuje wartość sterta 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.

  • Młode pokolenie: wszystkie nowe obiekty są przydzielane i przestarzałe w młodym pokoleniu.

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

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ęć niezwiązana z stertą jest podzielona na następujące części:

  • Część pamięci innej niż sterta, która zastąpiła trwałe generowanie (lub permGen) począwszy od języka Java 8. Siłownik 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 sterta i byłą częścią pamięci nienależącą do sterta. 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ń klasy, która jest dla skompresowanych wskaźników klasy.
    • Pamięć podręczna kodu, która przechowuje kod natywny skompilowany przez JIT.
  • Inna pamięć, taka jak stos wątków, który nie jest obserwowany przez siłownik Spring Boot.

Pamięć bezpośrednia

Pamięć bezpośrednia to pamięć natywna przydzielona przez java.nio.DirectByteBufferprogram , który jest używany w bibliotekach innych firm, takich jak nio i gzip.

Siłownik Spring Boot 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.

Odzyskiwanie pamięci w języku Java

Istnieją trzy terminy dotyczące odzyskiwania 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 miejscu uważamy, że "Główne GC" i "Full 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 obiekty na żywo z Przestrzeni Eden do s1 przestrzeni ocalałych lub z s1 do s2.

Pełne GC lub główne GC wykonuje odzyskiwanie pamięci w całej stercie. Pełna funkcja GC może również zbierać części, takie jak przestrzeń metadanych i pamięć bezpośrednia, które mogą być czyszczone tylko przez pełną 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 maksymalnego rozmiaru sterty na niższą wartość, odzyskiwanie pamięci odbywa się częściej, co spowalnia aplikację nieco, ale lepiej ogranicza użycie pamięci. Po ustawieniu maksymalnego rozmiaru sterty na wyższą wartość, odzyskiwanie pamięci odbywa się rzadziej, co może powodować większe ryzyko 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 bezpośredniego rozmiaru pamięci

    • -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

Usługa Azure Spring Apps ustawia domyślny maksymalny rozmiar pamięci sterty na około 50%-80% pamięci aplikacji dla aplikacji 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 będzie wynosić 60% pamięci aplikacji.
  • Jeśli 2 GB <= pamięć < aplikacji 3 GB, domyślny maksymalny rozmiar sterty będzie wynosić 70% pamięci aplikacji.
  • Jeśli 3 GB <= pamięć aplikacji, domyślny maksymalny rozmiar sterty będzie wynosić 80% pamięci aplikacji.

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 pamięci sterty. Aby uzyskać więcej informacji, zobacz plik JDK 8 VM.java.

Układ użycia pamięci

Rozmiar sterty ma wpływ na przepływność. Zasadniczo podczas konfigurowania można zachować domyślny maksymalny rozmiar sterty, który pozostawia rozsądną pamięć 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 siłownik Spring Boot)
      • Metaspace: wartość referencyjna dziennego użycia to 50M-256M
      • Pamięć podręczna kodu
      • Skompresowana przestrzeń klasy
    • Nie zaobserwowano części (nieobserwowane przez siłownik Spring Boot): wartość referencyjna dziennego użycia wynosi 150M-250M.
      • Stos wątków
      • GC, symbol wewnętrzny i inne
  • 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 aplikacja jest poza 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ż