İçeriğe geç

Prod'da Teşhis — Heap Dump, Thread Dump ve İlk On Beş Dakika

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

Önce şunu oku: Concurrency Araçları — Executor, Lock, Atomic ve Deadlock , Garbage Collection — Üçlü Takas

30 saniyede özet

Servis yavaşladığında önce belirtiye bakılır: bellek mi tırmanıyor, CPU mu yanıyor, thread'ler bir şey mi bekliyor? Her belirtinin bir aracı var. Kanıt, yeniden başlatmadan önce toplanır.

Doktora “halsizim” dediğinde doktor hemen ilaç yazmaz. Önce birkaç soru sorar, sonra belirtiye uyan tahlili ister.

Önce belirti, sonra araç.
Adım adım oku
  1. Hasta halsiz olduğunu söyler; bu henüz bir teşhis değil.
  2. Doktor belirtiyi sorar: ateş mi, öksürük mü, ağrı mı?
  3. Belirtiye uyan tek tahlili ister.
  4. Tahlil sonucu hastalığa bir ad koyar; tedavi ondan sonra gelir.
  1. Bayt: Destek ekibi yazıyor: havaleler bir saattir çok yavaş!

  2. Sen: Hemen yeniden başlatalım, düzelir.

  3. Bayt: Düzelir... ama neden yavaşladığını da yanına alıp gider.

  4. Bayt: Önce birkaç fotoğraf çekelim. Beş dakika sürer.

Önce belirti, sonra araç

Yavaşlığın arkasında genellikle üç şeyden biri vardır. Her biri başka bir iz bırakır:

Belirtiİlk bakılacak yer
Bellek günlerdir tırmanıyorGC sonrası kalan heap, sonra heap dumpJVM'in heap'indeki bütün nesnelerin bir dosyaya yazılmış anlık görüntüsü. Kimin kimi tuttuğunu gösterir; alınırken JVM durur.Eclipse MAT gibi araçlarla açılır. Bellekteki veriyi de içerdiği için müşteri verisi gibi korunur.Sözlükte gör →
Bir çekirdek sürekli yüzde yüzdetop -H ile hangi thread, sonra thread dump
CPU boşta ama istekler bitmiyorBirkaç thread dumpO anda JVM'deki bütün thread'lerin adını, durumunu ve yığınını gösteren metin. Uygulamayı durdurmadan saniyeler içinde alınır.jcmd PID Thread.print ya da jstack ile alınır. Birkaç saniye arayla birden fazla alınır ve tekrar eden desene bakılır.Sözlükte gör →

Bu araçların hepsi servis hâlâ hastayken çalışır. Yeniden başlatınca belirti de gider; bu yüzden kanıt önce alınır.

Kafam karıştı, daha basit anlat

Önce ne tür bir yavaşlık olduğunu sor: bellek mi, CPU mu, bekleme mi? Sonra ona uyan fotoğrafı çek.

Hızlı kontrolBaşlangıç

Üretimdeki bir servis yavaşladı ve kullanıcılar şikâyet ediyor. İlk iş ne yapılır?

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

Sızıntı mı, yük mü?

Heap grafiği bir testere gibidir: nesneler ayrılır, çizgi yükselir; GC toplar, çizgi düşer. Asıl bilgi dişlerin dibindedir.

Servisin heap'i öğleden sonra yüzde 85'e çıkıyor ve GC sık çalışıyor. Bu bir bellek sızıntısı mı? Cevabı göster

Buna bakarak söyleyemezsin. Sızıntıyı gösteren, GC’den sonra kalan heap’tir. Bu taban her gün biraz yükselip trafik düşünce inmiyorsa sızıntı, akşam eski yerine dönüyorsa yalnızca yüktür.

Sızıntı olduğu belliyse sıradaki soru, nesneleri kimin tuttuğudur. Heap dump’ı Eclipse MAT gibi bir araçla açıp nesneleri retained sizeBir nesne ortadan kalksaydı serbest kalacak toplam bellek: nesnenin kendisi ve yalnızca onun sayesinde canlı kalan her şey.Nesnenin kendi boyuna shallow size denir. Sızıntı ararken nesneler retained boya göre sıralanır.Sözlükte gör →’a göre sıralarsın. En üstte çoğunlukla bir static koleksiyon, bir cache ya da hiç çıkarılmayan bir dinleyici listesi durur.

Sızıntının tipik kaynaklarını Garbage Collection ve ThreadLocal derslerinde gördün. Burada önemli olan onları bulma yolu.

Kafam karıştı, daha basit anlat

Dolu heap tek başına bir şey söylemez. GC temizledikten sonra kalan her gün artıyorsa bir şey nesneleri bırakmıyordur; heap dump onun adını söyler.

Hızlı kontrolOrta

Hangi gözlem bir bellek sızıntısına işaret eder?

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

Heap dump'ta küçük bir HashMap nesnesi heap'in yüzde 70'ini "tutuyor" görünüyor, oysa nesnenin kendisi birkaç düzine byte. Bu nasıl olur?

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

Satır satır: bir thread dump okumak

CPU boşta, istekler bitmiyor. jcmd PID Thread.print ile bir thread dump aldın; iki bloğa birlikte bakalım.

Bekleyenler ve tutanlar

thread-dump.txt
1$ jcmd 4121 Thread.print
şu an çalışan satır"http-nio-8080-exec-12" #58 daemon prio=5 nid=0x4b12 waiting on condition
3 java.lang.Thread.State: TIMED_WAITING (parking)
4 at jdk.internal.misc.Unsafe.park(Native Method)
5 at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:181)
6 at com.bank.transfer.TransferService.send(TransferService.java:31)
7
8"http-nio-8080-exec-3" #49 daemon prio=5 nid=0x4a01 runnable
9 java.lang.Thread.State: RUNNABLE
10 at sun.nio.ch.SocketDispatcher.read0(Native Method)
11 at com.bank.fx.FxRateClient.fetch(FxRateClient.java:44)
12 at com.bank.transfer.TransferService.send(TransferService.java:24)

Debug

Adım 1/6

sen Her blok bir thread: adı, nid'i (işletim sistemindeki kimliği) ve kısa durumu.

thread
= exec-12
ShellUTF-8LF2:1

Sol/sağ ok tuşlarıyla da gezebilirsin.

Bu bir connection poolAçık veritabanı bağlantılarını tutan ve isteklere ödünç veren havuz. Bağlantı açmanın maliyetini bir kez öder; boyutu veritabanının kapasitesine göre seçilir.Sözlükte gör → tükenmesi. Tek dump bir fotoğraftır; birkaç saniye arayla üç dump alırsın ve aynı tablo tekrar ediyorsa desen budur.

CPU’yu tek bir thread yakıyorsa yol biraz farklıdır: top -H -p PID o thread’in kimliğini verir. Kimliği onaltılığa çevirip dump’taki nid alanında ararsın. Deadlock varsa JVM onu dump’ın sonunda kendisi raporlar; Concurrency araçları dersindeki gibi.

Hızlı kontrolOrta

CPU yüzde 5, ama istekler zaman aşımına düşüyor. Thread dump'ta 200 http thread'inin 190'ı TIMED_WAITING durumunda ve yığının tepesinde HikariPool.getConnection var. En olası açıklama?

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

top -H çıktısında TID 19007 olan thread yüzde 99 CPU kullanıyor. Thread dump'ta bu thread'i nasıl bulursun?

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

Kendin gör

Servis yavaşladı ve arkasında dört gizli sorundan biri var. Bir olay ve bir araç seç; araç sana o olayda ne gösterirdi, izle.

Prod yavaşladı: hangi araçla bakarsın?

Tohum 1
  • Yanıt süreleri günlerdir yavaş yavaş uzuyor.
  • GC loglarında toplama sıklaşmış.

Oynat ya da adımla.

Hız
Adım 0

Şu an ne oldu?

Servis yavaşladı

Yanıt süreleri günlerdir yavaş yavaş uzuyor. GC loglarında toplama sıklaşmış. Belirtiye bak ve aracı ona göre seç.

Görevler0/3

  • Sızıntıyı tutan alanı adıyla bulaçık

    İpucu

    Önce sızıntı olduğunu gör, sonra kimin tuttuğuna bak.

  • CPU boştayken yavaşlayan servisin neyi beklediğini bulaçık

    İpucu

    Thread'ler bir şey bekliyorsa ne beklediklerini sor.

  • Bir çekirdeği yakan kod satırını bulaçık

    İpucu

    Dump tek başına yetmez; hangi thread'in CPU yediğini de bilmen gerekir.

Olay günlüğü (0)

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

  1. Varsayılanı oynat. Gizli sorun A’ya thread dump ile bakıyorsun. Thread’ler normal: yanlış araç.
  2. Aracı “GC sonrası heap grafiği” yap. Taban tırmanıyor, sızıntı var ama kimin tuttuğu belli değil. Heap dump’a geç.
  3. Gizli sorun B’yi seç. CPU boşta: hangi araç bekleyenleri gösterir?
  4. Gizli sorun C ve D’yi dene. Birinde tek bir thread suçlu, ötekinde hiçbir araç suçlu bulamıyor. Bu da bir cevap.
Hızlı kontrolOrta

Her belirtiyi ilk bakılacak araca göre ayır.

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

Sınıflandırılmamış

GC sonrası heap, sonra heap dump

Bellek ile ilgili

    top -H ile thread başına CPU, sonra thread dump

    Bir thread CPU yakıyor

      Birkaç thread dump

      Thread'ler bir şey bekliyor

        Tuzaklar

        Önce yeniden başlatmak. Servis düzelir, kanıt gider, sorun geri gelir. Bir instance’ı trafikten çıkarıp kanıtı ondan al; diğerlerini sonra yeniden başlat.

        Trafikteyken heap dump. Dump boyunca JVM durur ve dosya heap kadar yer kaplar. İçinde IBAN’lar, token’lar ve müşteri adları olduğu için dosya müşteri verisi gibi korunur.

        Sebebi bilmeden büyütmek. Havuzu ya da heap’i büyütmek sızıntıyı ve yavaş dış çağrıyı yalnızca erteler. Önce teşhis, sonra ayar.

        Kafam karıştı, daha basit anlat

        Önce fotoğraf, sonra yeniden başlatma. Heap dump’ı trafikte olmayan bir instance’tan al ve dosyayı kasadaki para gibi koru.

        Hızlı kontrolİleri

        Kur servisi yavaşladığı günlerde bu havale servisi bütün uygulamayı kilitliyor ve thread'ler bağlantı bekliyor. Sorunun kaynağı hangi satır?

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

        Hatalı satıra dokun, sonra kontrol et.

        TransferService.java
        Java 21UTF-8LF
        Derinleş · Havale servisi: kanıt betiği, hazır JVM ayarları ve düzeltilmiş servis 4 dosya · ~54 satır · ilk okumada atlayabilirsin
        Proje dosyaları

        ops/ collect-evidence.sh Kanıt betiği: beş saniye arayla üç thread dump, thread başına CPU, heap özeti, sınıf histogramı ve iki dakikalık JFR kaydı. Hepsi saniyeler içinde biter ve JVM'i durdurmaz; tam heap dump ayrı ve bilinçli bir adımdır.

        ops/collect-evidence.sh
        #!/usr/bin/env bash
        # Usage: collect-evidence.sh <pid>. Run it on an instance taken out of the load balancer.
        set -euo pipefail
        PID="$1"
        OUT="/var/evidence/$(date +%Y%m%d-%H%M%S)"
        mkdir -p "$OUT"
        for i in 1 2 3; do
        jcmd "$PID" Thread.print -l > "$OUT/threads-$i.txt"
        sleep 5
        done
        top -H -b -n 1 -p "$PID" | head -30 > "$OUT/top-threads.txt"
        jcmd "$PID" GC.heap_info > "$OUT/heap-info.txt"
        jcmd "$PID" GC.class_histogram | head -40 > "$OUT/histogram.txt"
        jcmd "$PID" JFR.start name=incident duration=120s filename="$OUT/incident.jfr" settings=profile
        echo "Evidence in $OUT. A full heap dump pauses the JVM: take it only on purpose."

        ops/ jvm-flags.conf JVM ayarları: OOM anında dump otomatik alınır, GC logu hep açıktır. Olay gece yaşansa da kanıt sabah bekler.

        ops/jvm-flags.conf
        -XX:+HeapDumpOnOutOfMemoryError
        -XX:HeapDumpPath=/var/evidence/oom.hprof
        -Xlog:gc*:file=/var/log/app/gc.log:time,uptime:filecount=5,filesize=20m

        src/main/resources/ application.yml Havuz ayarı: bir bağlantı uzun süre tutulursa HikariCP bunu tutanın yığınıyla loglar. Sızıntıyı bekleyenlerden değil, tutandan görürsün.

        src/main/resources/application.yml
        spring:
        datasource:
        hikari:
        maximum-pool-size: 10
        connection-timeout: 3000
        # A connection held longer than this is logged together with the stack of the thread holding it.
        leak-detection-threshold: 2000

        src/main/java/com/bank/transfer/ TransferService.java Servis: kur transaction'ın dışında alınır. Bağlantı ve satır kilidi yalnızca veritabanı işi süresince tutulur.

        src/main/java/com/bank/transfer/TransferService.java
        @Service
        class TransferService {
        private final AccountRepository accounts;
        private final TransferRepository transfers;
        private final FxRateClient fxClient;
        private final TransactionTemplate tx;
        TransferService(AccountRepository accounts, TransferRepository transfers,
        FxRateClient fxClient, TransactionTemplate tx) {
        this.accounts = accounts;
        this.transfers = transfers;
        this.fxClient = fxClient;
        this.tx = tx;
        }
        public Transfer send(TransferRequest r) {
        // The slow remote call happens before any connection or row lock is taken.
        BigDecimal rate = fxClient.fetchRate(r.currency());
        return tx.execute(status -> {
        Account from = accounts.lockById(r.fromId());
        from.withdraw(r.amount().multiply(rate));
        return transfers.save(Transfer.of(from, r, rate));
        });
        }
        }

        Kendini sına

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

        Şimşek turu1/5

        Heap'in yüzde 85 dolu olması tek başına bir sızıntı olduğunu gösterir.

        Soru 1/3İleri

        Üretimde 16 GB heap'li bir JVM'den heap dump alacaksın. Hangisini hesaba katman gerekir?

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

        Aklında kalacak üç şey

        1. 1 Önce belirti, sonra araç. Bellek tırmanıyorsa GC sonrası heap ve heap dump; bir çekirdek yanıyorsa top -H ve thread dump; CPU boştayken yavaşsa birkaç thread dump.
        2. 2 Sızıntıyı heap'in doluluğu değil, GC'den sonra kalan taban gösterir. Heap dump'ta suçlu, retained boyu en büyük tutucudur.
        3. 3 Yeniden başlatmak kanıtı siler. Bir instance trafikten çıkarılır, kanıt ondan alınır; heap dump JVM'i durdurur ve müşteri verisi içerir.
        Sonraki kapı Pod her gece yeniden başlıyor ama loglarda tek bir hata yok. Kim öldürüyor? JVM Konteynerde — Bellek Sınırı Heap'ten Büyüktür · 10 dk

        4 kart sonraki derste seni bekliyor

        0/4 kart bu dersten toplandı