İçeriğe geç

JVM Konteynerde — Bellek Sınırı Heap'ten Büyüktür

İleri 10 dk Çok sık karşılaşılır

Önce şunu oku: Garbage Collection — Üçlü Takas

30 saniyede özet

Konteynerin bellek sınırı bütün süreci sayar: heap, metaspace, thread yığınları ve native bellek. Heap'e sınırın tamamını verirsen süreç sessizce öldürülür; hiç ayar vermezsen heap dörtte birde kalır.

Asansörde “en fazla 600 kg” yazar. Yolcuları sayarsın, sığıyor; sonra herkes valizini de içeri çeker.

JVM’i bir konteynere koyduğunda bellek sınırı tam bu tartı gibi çalışır. Heap yolculardır; ama tartı valizleri de sayar.

Sınır yalnızca yolcuları değil, valizleri de sayar.
Adım adım oku
  1. Asansörün sınırı 600 kg.
  2. Yolcular biniyor; tartı 240 kg gösteriyor.
  3. Valizler de biniyor; tartı 580 kg'a çıkıyor.
  4. Tartı ayırmaz: yolcu da valiz de aynı sınırdan yer.
  1. Bayt: Ödeme servisi her gece yeniden başlıyor. Loglarda tek bir hata yok!

  2. Sen: Heap'e konteynerin tamamını, 2 GB verdik. Yetmiyor mu?

  3. Bayt: Sorun belki tam da bu. Heap 2 GB ise geri kalan her şey nereye sığıyor?

  4. Bayt: Önce JVM'in belleğini kalem kalem açalım.

Heap, sürecin yalnızca bir parçası

KalemNe tutar
HeapSenin nesnelerin
metaspaceJVM'in yüklenen sınıfların bilgisini heap dışında tuttuğu bellek alanı. Varsayılan olarak tavanı yoktur; çok sınıf yükleyen uygulamada büyür.Java 8'de PermGen'in yerini aldı. MaxMetaspaceSize ile sınırlanabilir; sınıf yükleyici sızıntıları burada görünür.Sözlükte gör →Yüklenen sınıfların bilgisi
Thread yığınlarıHer platform thread’i için ayrı bir yığın, varsayılan 1 MiB
Code cacheJIT’in ürettiği makine kodu
GC yapıları ve direct buffer’larToplayıcının defterleri, ağ ve dosya tamponları

Konteynerin sınırını Linux’ta cgroupLinux çekirdeğinin bir süreç grubuna bellek ve CPU sınırı koyduğu mekanizma. Docker ve Kubernetes konteyner sınırlarını bununla uygular.JDK 10'dan beri JVM sınırları cgroup'tan okur (UseContainerSupport). Bellek sınırı sürecin bütün belleğini sayar.Sözlükte gör → koyar ve bu kalemlerin hepsini birlikte sayar. Spring Boot’lu tipik bir serviste heap dışı kalemler yüzlerce MiB tutabilir.

Kafam karıştı, daha basit anlat

Heap senin nesnelerin. Ama JVM’in kendi eşyaları da var ve onlar da aynı odada duruyor.

Hızlı kontrolOrta

Bir Java servisinin konteyner bellek sınırına neler sayılır?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Her şeyin JVM belleğinde nerede durduğunu seç.

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sınıflandırılmamış

Heap

GC'nin topladığı nesneler

    Heap dışı

    Aynı süreçte ama heap'in dışında

      JVM heap’i nasıl seçer?

      JDK 10’dan beri JVM konteynerde çalıştığını anlar ve sınırı cgroup’tan okur. Heap’in tavanını bu sınıra göre seçer.

      Sınırı 2 GiB olan bir konteynerde Java 21 servisini hiçbir bellek ayarı vermeden başlattın. Heap'in tavanı ne olur? Cevabı göster

      Yaklaşık 512 MiB, sınırın dörtte biri. MaxRAMPercentage ayarının varsayılan değeri 25’tir. Konteynerdeki belleğin dörtte üçü heap’e hiç verilmez.

      Heap’i sabit bir -Xmx ile değil, sınırın bir yüzdesiyle vermek daha güvenlidir. Sınır büyüyüp küçüldüğünde ayar da onunla gelir.

      Heap'i sınırın bir yüzdesi olarak ver
      java -XX:MaxRAMPercentage=50 -jar payment.jar
      Kafam karıştı, daha basit anlat

      Hiçbir şey söylemezsen JVM çekingen davranır ve odanın dörtte birini alır. Yüzde ile söylersen ne kadar alacağını bilir.

      Hızlı kontrolOrta

      Sınırı 2 GiB olan bir konteynerde Java 21 servisi hiç bellek ayarı olmadan başlıyor. Heap'in tavanı yaklaşık ne olur?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Heap'i neden sabit bir -Xmx yerine -XX:MaxRAMPercentage ile vermek daha iyi?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      İki farklı ölüm

      Heap yetmezse JVM OutOfMemoryError fırlatır. Logda yığın izi olur, ayarlıysan bir heap dump da alınır.

      Bütün süreç sınırı geçerse karar Java’nın değil, çekirdeğin OOM killerBellek sınırı aşılınca Linux çekirdeğinin bir süreci SIGKILL ile anında sonlandırması. Süreç hiçbir şey yazamaz; çıkış kodu 137 olur.Kubernetes bunu konteyner durumunda OOMKilled olarak gösterir. Java'nın OutOfMemoryError'ından farklıdır: o JVM'in içinde fırlatılır ve iz bırakır.Sözlükte gör →’ının olur. Süreç SIGKILL ile anında biter: çıkış kodu 137, Kubernetes’te durum OOMKilled. Java tek satır bile yazamaz.

      Heap dışı kalemleri görmek için Native Memory Tracking açılır: -XX:NativeMemoryTracking=summary ve sonra jcmd PID VM.native_memory summary.

      Hızlı kontrolOrta

      Bir pod gece yeniden başladı. Çıkış kodu 137 ve uygulama logunda hiçbir hata yok. En olası açıklama?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Kendin gör

      2 GiB’lık konteynerde bir ödeme servisi var ve trafik tepe noktasına çıkıyor. Heap ayarını ve thread sayısını seç, belleğin nereye gittiğini izle.

      2 GiB'lık konteyner: bellek nereye gidiyor?

      Tohum 1

      Oynat ya da adımla.

      Hız
      Adım 0

      Şu an ne oldu?

      Ödeme servisi açıldı

      Konteynerin bellek sınırı 2048 MiB. Heap'in tavanı 2048 MiB. Trafik birazdan tepe noktasına çıkacak.

      Görevler0/3

      • Hiçbir Java hatası yazmadan ölen süreci göraçık

        İpucu

        Heap'e konteynerin bütün belleğini verirsen ne kalır?

      • Konteynerde boş bellek varken heap'in yetmediğini göraçık

        İpucu

        Hiç ayar yapmazsan JVM ne kadar heap seçer?

      • 400 thread ile tepe noktasını sağ atlataçık

        İpucu

        Heap'e yeterince ver, ama thread yığınlarına da yer bırak.

      Olay günlüğü (0)

      Henüz olay yok. Oynat veya adımla.

      1. Varsayılanı oynat. -Xmx2g: heap büyüdükçe toplam sınırı geçiyor ve süreç sessizce ölüyor.
      2. “Hiç ayar yok”u seç. Bu kez heap dar: konteynerde boş yer varken OutOfMemoryError.
      3. Yüzde 75’i dene, sonra 400 thread’i aç. Thread yığınları da sınırdan yiyor.
      4. 400 thread’le tepe noktasını sağ atlatan ayarı bul.
      Hızlı kontrolOrta

      Simülatörde MaxRAMPercentage=75 az thread'le ayakta kaldı ama 400 thread'de süreç öldürüldü. Neden?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Tuzaklar

      -Xmx’i sınıra eşitlemek. Heap dışı kalemlere yer kalmaz; süreç yükte öldürülür.

      Çok küçük CPU sınırı. JVM işlemci sayısını da konteynerden okur. İkiden az işlemci görürse G1 yerine Serial GC seçer ve havuzlarını küçük kurar.

      Sınırı ölçmeden seçmek. Heap dışı pay her serviste farklıdır; NMT ile bir kez ölçülür ve yüzde ona göre ayarlanır.

      Hızlı kontrolİleri

      Bu ödeme servisi yoğun saatte iz bırakmadan yeniden başlıyor ve GC duraklamaları uzun. Sorunlu satırları işaretle.

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Hatalı satıra dokun, sonra kontrol et.

      run-payment.sh
      ShellUTF-8LF
      Derinleş · Ödeme servisi: konteynere uygun JVM ayarları 4 dosya · ~36 satır · ilk okumada atlayabilirsin
      Proje dosyaları

      Dockerfile İmaj: heap sınırın yüzdesi olarak verilir; OOM'da heap dump kalıcı bir diske yazılır, NMT açıktır.

      Dockerfile
      FROM eclipse-temurin:21-jre
      COPY build/libs/payment.jar /app/payment.jar
      # Heap as a share of the container limit, measured with NMT; the rest is native headroom.
      ENV JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=60 \
      -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dumps \
      -XX:NativeMemoryTracking=summary"
      ENTRYPOINT ["java", "-jar", "/app/payment.jar"]

      k8s/ payment-deployment.yaml Kubernetes: bellek isteği ve sınırı aynı; CPU isteği en az iki çekirdek.

      k8s/payment-deployment.yaml
      spec:
      template:
      spec:
      containers:
      - name: payment
      image: registry.bank.local/payment:1.8.0
      resources:
      requests:
      memory: "2Gi"
      cpu: "2"
      limits:
      memory: "2Gi"
      volumeMounts:
      - name: dumps
      mountPath: /dumps
      volumes:
      - name: dumps
      persistentVolumeClaim:
      claimName: payment-dumps

      src/main/resources/ application.yml Servis: platform thread sayısı bilerek sınırlanır; her thread'in yığını sınırdan yer.

      src/main/resources/application.yml
      server:
      tomcat:
      threads:
      # Each platform thread has its own stack outside the heap.
      max: 150

      ops/ memory-breakdown.sh Ölçüm betiği: süreç içinden native bellek özeti ve heap özeti.

      ops/memory-breakdown.sh
      #!/usr/bin/env bash
      # Usage: memory-breakdown.sh <pid>. Prints where the process memory goes.
      set -euo pipefail
      jcmd "$1" VM.native_memory summary | head -60
      jcmd "$1" GC.heap_info

      Kendini sına

      Önce hızlı bir ısınma: puan yok, kayıt yok. Sonra asıl sorular.

      Şimşek turu1/4

      Konteynerin bellek sınırı yalnızca heap'i sayar.

      Soru 1/3İleri

      Heap sabit ama sürecin belleği günler içinde yavaşça artıyor ve sonunda OOMKilled oluyor. Nasıl ilerlersin?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Aklında kalacak üç şey

      1. 1 Konteyner sınırı heap'in değil bütün sürecin sınırıdır. Metaspace, thread yığınları, code cache ve native bellek de aynı sınırdan yer.
      2. 2 Ayar yoksa JVM heap'e sınırın dörtte birini verir. Heap, -Xmx yerine MaxRAMPercentage ile sınırın bir yüzdesi olarak verilir ve heap dışına pay kalır.
      3. 3 Heap yetmezse JVM OutOfMemoryError fırlatır ve iz bırakır. Süreç sınırı aşarsa çekirdek onu öldürür: OOMKilled, çıkış kodu 137, Java'dan tek satır yok.
      Sonraki kapı Yeni sürümü yayına aldın: ilk dakikalar yavaş, sonra her şey hızlanıyor. Neden? Sınıf Yükleme ve JIT — Uygulama Neden Isınır? · 9 dk

      5 kart sonraki derste seni bekliyor

      0/4 kart bu dersten toplandı

      Bu dersin üstüne kurulanlar

      Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.