Bellek Sızıntısı ve Referans Türleri — GC Neyi Bırakabilir?
Ö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.
Adım adım oku
- Depoya üç kutu konuyor.
- Her kutunun üstünde bir not var: dokunma, yer lazımsa at, sahipsizse at.
- Temizlikçi, yani GC, depoya giriyor.
- Kutulara değil notlara bakıyor. Neyin kalacağını not belirliyor.
-
Bayt: Ekstre sayfalarını bir map'te saklıyorum. İkinci açılış çok hızlı!
-
Sen: Ama sunucu her pazartesi yeniden başlatılıyor. Yoksa bellek doluyor.
-
Bayt: Java'da çöp toplayıcı yok muydu? Neden temizlemiyor?
-
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:
| Kaynak | Neden sızıyor |
|---|---|
| Hep büyüyen static bir map ya da liste | Static alan bir köktür; içine konan her şeye ulaşılır |
| Kaydolup hiç ayrılmayan dinleyici | Olay kaynağı dinleyiciyi, dinleyici de ekranı tutar |
| Sınırı olmayan bir cache | Silme kuralı yoksa cache bir static map’ten farksızdır |
| Havuzdaki thread’de temizlenmeyen ThreadLocal | Thread hiç ölmez, değeri de ölmez |
| HashSet’e konduktan sonra hashCode’u değişen nesne | Artı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.
Java'da çöp toplayıcı var. Buna rağmen bellek sızıntısı nasıl olur?
Her durumu sızıntı olup olmadığına göre ayır.
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ür | Depodaki not | GC ne yapar? |
|---|---|---|
| Güçlü | dokunma | Ulaşılabildiği sürece asla toplamaz |
SoftReference | yer lazımsa at | Bellek bolken bırakır, sıkışınca temizler |
WeakReference | sahipsizse at | Başka güçlü referans yoksa ilk GC’de temizler |
PhantomReference | gittiğinde haber ver | Nesneyi 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.
WeakReference<Page> ref = new WeakReference<>(new Page("A"));Page page = ref.get(); // ya sayfa ya null: her get'ten sonra kontrol etKafam 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.
Bellek bol. Bir nesneye yalnızca bir WeakReference var ve GC çalışıyor. Ne olur?
SoftReference hakkında Java'nın belgelenmiş sözü hangisi?
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
Map<Screen, Notes> notes = new WeakHashMap<>();Screen screen = new Screen("A");notes.put(screen, new Notes(screen));screen = null;System.gc();notes.size();Debug
sen Anahtarları zayıf tutan bir map.
- notes
- = boş
Sol/sağ ok tuşlarıyla da gezebilirsin.
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?
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 1Oynat ya da adımla.
Ş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.
- Varsayılanı oynat. Static map dört sayfayı bırakmıyor: GC çalışsa da “sızıntı” yazıyor.
- “Sonunda heap sıkışsın”ı aç. Aynı cache OutOfMemoryError’a kadar gidiyor.
- WeakReference’a geç. Bellek bolken bile dört sayfa ilk GC’de gidiyor. Tekrar gelen müşteri cache’i boş bulur.
- SoftReference’ı heap sıkışıkken dene, sonra sınırlı cache’i. Hangisinin davranışını önceden bilebiliyorsun?
Simülatörde sınırlı cache (en fazla 3) neden hiç sızıntı göstermedi, oysa sayfaları güçlü referansla tutuyordu?
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.
Kur ekranı her açılıp kapandığında bellek biraz artıyor. Sızıntıya yol açan satırları işaretle.
İ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
Kendini sına
Önce hızlı bir ısınma: puan yok, kayıt yok. Sonra asıl sorular.
Java'da çöp toplayıcı olduğu için bellek sızıntısı olamaz.
Ekip ekstre cache'ini SoftReference değerlerle kurmak istiyor: 'Bellek dolunca kendiliğinden boşalır.' En güçlü itiraz hangisi?
Aklında kalacak üç şey
- 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 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 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.
4 kart sonraki derste seni bekliyor