İçeriğe geç

Bellek Sızıntısı ve Referans Türleri — GC Neyi Bırakabilir?

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

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

30 saniyede özet

Java'da çöp toplayıcı var ama sızıntı da var: kullanılmayan bir nesneye hâlâ ulaşılabiliyorsa GC onu bırakamaz. Zayıf ve yumuşak referanslar GC'ye 'bunu atabilirsin' demenin yolu; sınırlı bir cache ise en sağlamı.

Evdeki depo her ay biraz daha doluyor. Her şeyi bir gün lazım olur diye saklıyorsun ama çoğuna bir daha hiç bakmıyorsun.

Java’nın çöp toplayıcısı da böyle bir depoyu temizler. Yalnız bir kuralı var: sahibi olan hiçbir şeye dokunmaz.

Temizlikçi kutuyu açmaz, üstündeki nota bakar.
Adım adım oku
  1. Depoya üç kutu konuyor.
  2. Her kutunun üstünde bir not var: dokunma, yer lazımsa at, sahipsizse at.
  3. Temizlikçi, yani GC, depoya giriyor.
  4. Kutulara değil notlara bakıyor. Neyin kalacağını not belirliyor.
  1. Bayt: Ekstre sayfalarını bir map'te saklıyorum. İkinci açılış çok hızlı!

  2. Sen: Ama sunucu her pazartesi yeniden başlatılıyor. Yoksa bellek doluyor.

  3. Bayt: Java'da çöp toplayıcı yok muydu? Neden temizlemiyor?

  4. Bayt: Çünkü o map sayfaları hâlâ tutuyor. GC'ye göre hepsinin bir sahibi var.

GC varken sızıntı nasıl olur?

GC bir nesneyi toplamadan önce tek bir şey sorar: buna bir GC köküGC'nin canlı nesneleri aramaya başladığı yer: çalışan thread'lerin yerel değişkenleri, static alanlar ve JNI referansları. Buradan ulaşılamayan nesne çöptür.Heap dump araçlarındaki 'path to GC roots' komutu, bir nesneyi canlı tutan zinciri kökten nesneye kadar gösterir.Sözlükte gör → ulaşılabiliyor mu? Çalışan bir thread’in değişkenleri ve static alanlar bu köklerdendir.

Ulaşılabilen her nesne canlı sayılır, kullanılsa da kullanılmasa da. bellek sızıntısıArtık kullanılmayan ama hâlâ bir kökten ulaşılabildiği için GC'nin toplayamadığı nesneler. Genellikle static bir koleksiyon, sınırsız bir cache ya da çıkarılmayan bir dinleyici.İşareti, GC sonrası kalan heap'in zamanla yükselmesidir. Sonunda OutOfMemoryError ya da sık ve uzun GC duraklamaları gelir.Sözlükte gör → tam olarak budur: bir daha işine yaramayacak ama hâlâ ulaşılabilen nesneler.

En sık görülen beş sızıntı şöyle:

KaynakNeden sızıyor
Hep büyüyen static bir map ya da listeStatic alan bir köktür; içine konan her şeye ulaşılır
Kaydolup hiç ayrılmayan dinleyiciOlay kaynağı dinleyiciyi, dinleyici de ekranı tutar
Sınırı olmayan bir cacheSilme kuralı yoksa cache bir static map’ten farksızdır
Havuzdaki thread’de temizlenmeyen ThreadLocalThread hiç ölmez, değeri de ölmez
HashSet’e konduktan sonra hashCode’u değişen nesneArtık bulunamaz, bu yüzden silinemez de
Kafam karıştı, daha basit anlat

GC bir nesneyi ancak kimse ona ulaşamıyorsa atar. Unuttuğun bir listede duran nesne de “ulaşılabilir” sayılır, o yüzden atılmaz.

Hızlı kontrolBaşlangıç

Java'da çöp toplayıcı var. Buna rağmen bellek sızıntısı nasıl olur?

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

Her durumu sızıntı olup olmadığına göre ayır.

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

Sınıflandırılmamış

Sızıntı

Kullanılmıyor ama ulaşılabilir

    Sızıntı değil

    GC toplayabilir ya da tavan var

      Dört referans türü

      Şimdiye kadar yazdığın her referans güçlü referanstır: Page p = new Page(); Güçlü referansla tutulan nesneye GC dokunmaz.

      java.lang.ref paketi GC’ye “bunu atabilirsin” demenin üç yolunu daha verir:

      TürDepodaki notGC ne yapar?
      GüçlüdokunmaUlaşılabildiği sürece asla toplamaz
      SoftReferenceyer lazımsa atBellek bolken bırakır, sıkışınca temizler
      WeakReferencesahipsizse atBaşka güçlü referans yoksa ilk GC’de temizler
      PhantomReferencegittiğinde haber verNesneyi hiç geri vermez; sadece toplandığını bildirir

      yumuşak referansBellek bolken nesneyi bırakan, sıkışınca temizlenen referans (SoftReference). OutOfMemoryError'dan önce bütün yumuşak referanslı nesneler temizlenir.Ne zaman temizleneceği JVM'e bağlıdır; HotSpot'ta son erişim zamanına ve boş heap'e bakılır. Bu yüzden cache için tahmin edilebilir bir araç değildir.Sözlükte gör → için Java’nın bir sözü var: bu nesneler OutOfMemoryError fırlatılmadan önce mutlaka temizlenir. zayıf referansNesneyi tek başına canlı tutmayan referans (WeakReference). Nesneye başka güçlü referans yoksa ilk GC'de temizlenir ve get() null döner.WeakHashMap anahtarlarını bu yolla tutar. Belleğin bol ya da dar olmasına bakılmaz.Sözlükte gör → ise belleğin durumuna hiç bakmaz.

      Zayıf referansın içi boşalabilir
      WeakReference<Page> ref = new WeakReference<>(new Page("A"));
      Page page = ref.get(); // ya sayfa ya null: her get'ten sonra kontrol et
      Kafam karıştı, daha basit anlat

      Güçlü referans “bunu sakın atma” der. Yumuşak “yer lazım olursa at”, zayıf “benden başka tutan yoksa at” der.

      HotSpot yumuşak referansı ne kadar tutar?· istersen atla

      HotSpot son erişim zamanına ve boş heap’e bakar: -XX:SoftRefLRUPolicyMSPerMB varsayılan olarak boş heap’in her megabaytı için 1000 ms tanır. Bu bir uygulama ayrıntısıdır.

      Hızlı kontrolOrta

      Bellek bol. Bir nesneye yalnızca bir WeakReference var ve GC çalışıyor. Ne olur?

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

      SoftReference hakkında Java'nın belgelenmiş sözü hangisi?

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

      Satır satır: WeakHashMap girdisi ne zaman silinir?

      WeakHashMap anahtarlarını zayıf referansla tutar. Anahtara başka güçlü referans kalmazsa girdi kendiliğinden silinir.

      Bir WeakHashMap'te anahtar olan ekran nesnesini artık kimse kullanmıyor. Ama değer olarak konan nesne, alanlarından birinde o ekranı tutuyor. GC çalışınca bu girdi silinir mi? Cevabı göster

      Hayır, hiç silinmez. WeakHashMap anahtarı zayıf tutar ama değeri güçlü tutar. Değer anahtarı gösterdiği için anahtara map’in kendisinden güçlü bir yol kalır ve anahtar hiç “sahipsiz” olmaz.

      Değer anahtarı tutunca

      ScreenNotes.java
      şu an çalışan satırMap<Screen, Notes> notes = new WeakHashMap<>();
      2Screen screen = new Screen("A");
      3notes.put(screen, new Notes(screen));
      4screen = null;
      5System.gc();
      6notes.size();

      Debug

      Adım 1/5

      sen Anahtarları zayıf tutan bir map.

      notes
      = boş
      Java 21UTF-8LF1:1

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

      Hızlı kontrolİleri

      Bir WeakHashMap'te değer nesnesi, alanlarından birinde kendi anahtarını tutuyor. Anahtara dışarıdan başka referans yok. Girdi ne zaman silinir?

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

      Kendin gör

      Altı müşteri ekstresini açıyor, dördü çıkış yapıyor, sonra GC çalışıyor. Cache’in sayfaları nasıl tuttuğunu seç ve neyin kaldığına bak.

      Ekstre cache'i: GC neyi toplayabilir?

      Tohum 1

      Oynat ya da adımla.

      Hız
      Adım 0

      Şu an ne oldu?

      Sabah, cache boş

      Altı müşteri ekstresini açacak. Cache sayfaları şöyle tutuyor: static HashMap (güçlü). Oynat ve GC'nin neyi toplayabildiğine bak.

      Görevler0/3

      • GC'nin toplayamadığı dört sayfayı göraçık

        İpucu

        Sayfaları hiç bırakmayan bir cache seç ve GC adımına kadar oynat.

      • Bellek bolken bile toplanan sayfaları göraçık

        İpucu

        Hangi referans, nesneyi tek başına canlı tutamaz?

      • Heap sıkışınca temizlenen ama hata vermeyen cache'i bulaçık

        İpucu

        Bellek bolken dokunulmayan referans hangisiydi?

      Olay günlüğü (0)

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

      1. Varsayılanı oynat. Static map dört sayfayı bırakmıyor: GC çalışsa da “sızıntı” yazıyor.
      2. “Sonunda heap sıkışsın”ı aç. Aynı cache OutOfMemoryError’a kadar gidiyor.
      3. WeakReference’a geç. Bellek bolken bile dört sayfa ilk GC’de gidiyor. Tekrar gelen müşteri cache’i boş bulur.
      4. SoftReference’ı heap sıkışıkken dene, sonra sınırlı cache’i. Hangisinin davranışını önceden bilebiliyorsun?
      Hızlı kontrolOrta

      Simülatörde sınırlı cache (en fazla 3) neden hiç sızıntı göstermedi, oysa sayfaları güçlü referansla tutuyordu?

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

      Tuzaklar

      WeakHashMap’i cache sanmak. Zayıf olan anahtardır, değer değil. Silinme belleğe göre değil, anahtarın sahibine göre olur.

      Lambda’yı zayıf bir dinleyici listesine koymak. Lambda’yı başka kimse tutmuyorsa ilk GC’de kaybolur ve olaylar sessizce kesilir.

      Her cache’i SoftReference ile yapmak. Bellek sıkışınca bütün cache bir anda boşalır ve GC daha çok çalışır.

      finalize ile temizlik. Ne zaman çalışacağı belli değildir ve Java 18’de kaldırılmak üzere işaretlendi. Kaynakları try-with-resources ile kapat; son çare olarak java.lang.ref.Cleaner var.

      Kafam karıştı, daha basit anlat

      Cache’e bir sınır koy, dinleyiciyi kapatırken listeden çıkar. Zayıf ve yumuşak referansları ancak neden gerektiğini söyleyebiliyorsan kullan.

      Hızlı kontrolİleri

      Kur ekranı her açılıp kapandığında bellek biraz artıyor. Sızıntıya yol açan satırları işaretle.

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

      Hatalı satıra dokun, sonra kontrol et.

      FxScreen.java
      Java 21UTF-8LF

      İnternet şubesinden bir örnek: sınırlı bir ekstre cache’i ve ekran kapanınca kapanan bir kur aboneliği.

      Derinleş · İnternet şubesi: sınırlı ekstre cache'i ve kapanan abonelik 3 dosya · ~52 satır · ilk okumada atlayabilirsin
      Proje dosyaları

      src/main/java/com/bank/statement/ StatementCache.java Ekstre cache'i: Caffeine ile en fazla on bin sayfa ve on beş dakikalık ömür. Tavanı GC değil, sen belirliyorsun.

      src/main/java/com/bank/statement/StatementCache.java
      @Component
      public class StatementCache {
      private final Cache<StatementKey, StatementPage> pages = Caffeine.newBuilder()
      .maximumSize(10_000)
      .expireAfterAccess(Duration.ofMinutes(15))
      .build();
      private final StatementRepository repository;
      public StatementCache(StatementRepository repository) {
      this.repository = repository;
      }
      public StatementPage page(StatementKey key) {
      return pages.get(key, repository::load);
      }
      public void evictCustomer(String customerId) {
      pages.asMap().keySet().removeIf(key -> key.customerId().equals(customerId));
      }
      }

      src/main/java/com/bank/fx/ FxRateFeed.java Kur akışı: subscribe bir abonelik döndürür. Aboneliği kapatmak dinleyiciyi listeden çıkarır.

      src/main/java/com/bank/fx/FxRateFeed.java
      @Component
      public class FxRateFeed {
      private final Set<Consumer<FxRate>> listeners = ConcurrentHashMap.newKeySet();
      /** The caller keeps the subscription and closes it when it no longer listens. */
      public AutoCloseable subscribe(Consumer<FxRate> listener) {
      listeners.add(listener);
      return () -> listeners.remove(listener);
      }
      void publish(FxRate rate) {
      listeners.forEach(listener -> listener.accept(rate));
      }
      }

      src/main/java/com/bank/statement/ StatementScreen.java Ekstre ekranı: aboneliği alanında tutar, ekran kapanınca kapatır. Dinleyici ekranı, liste de dinleyiciyi artık tutmaz.

      src/main/java/com/bank/statement/StatementScreen.java
      public class StatementScreen implements AutoCloseable {
      private final AutoCloseable fxSubscription;
      private final List<String> lines = new ArrayList<>();
      public StatementScreen(FxRateFeed feed) {
      // The lambda captures this screen: while the feed holds the lambda, it holds the screen.
      this.fxSubscription = feed.subscribe(rate -> lines.add("Kur: " + rate));
      }
      @Override
      public void close() throws Exception {
      fxSubscription.close();
      }
      }

      Kendini sına

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

      Şimşek turu1/4

      Java'da çöp toplayıcı olduğu için bellek sızıntısı olamaz.

      Soru 1/3İleri

      Ekip ekstre cache'ini SoftReference değerlerle kurmak istiyor: 'Bellek dolunca kendiliğinden boşalır.' En güçlü itiraz hangisi?

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

      Aklında kalacak üç şey

      1. 1 GC ulaşılabilen her nesneyi canlı sayar. Sızıntı, artık kullanılmayan ama hâlâ bir static alandan, cache'ten ya da dinleyici listesinden ulaşılan nesnedir.
      2. 2 Zayıf referans nesneyi tek başına canlı tutamaz; yumuşak referans bellek sıkışana kadar dayanır ve OutOfMemoryError'dan önce temizlenir.
      3. 3 Cache için en sağlam çözüm boyutu ve süresi sınırlı bir cache'tir. WeakHashMap anahtarı zayıf tutar, değeri değil; değer anahtarı gösterirse girdi hiç silinmez.
      Sonraki kapı Servis yavaşladı ve ilk refleksin yeniden başlatmak. Neyi kaybediyorsun? Prod'da Teşhis — Heap Dump, Thread Dump ve İlk On Beş Dakika · 10 dk

      4 kart sonraki derste seni bekliyor

      0/4 kart bu dersten toplandı