JavaProgramlamaKıdemli Java Geliştirici

ThreadLocalMap'ın giriş depolamasının hangi spesifik özelliği, ilişkili ThreadLocal anahtarları null olsa bile çöp toplayıcının değer nesnelerini geri almasını engeller?

Hintsage yapay zeka asistanı ile mülakatları geçin

Sorunun yanıtı.

ThreadLocal, Java 1.2'de method parametre geçişi olmadan thread-local değişkenler sağlamak için tanıtıldı. Uygulama, her Thread nesnesinde depolanan bir ThreadLocalMap kullanmaktadır; burada haritanın anahtarları ThreadLocal örneklerinin etrafında sarılmış WeakReference sarmalayıcılarıdır. Kritik tasarım hatası, haritanın Entry sınıfının değeri güçlü bir referans alanı aracılığıyla tutmasındadır; bu, WeakReference anahtarı çöp toplama ile temizlendiğinde bile değer nesnesinin yaşayan Thread tarafından güçlü bir şekilde referans alındığı anlamına gelir. Bu, thread havuzlarında, thread'lerin sürekli olarak hayatta kaldığı ve yetim değerleri biriktirdiği bir bellek sızıntısına neden olur. remove() çağrısı yapılmadıkça, bozuk giriş thread'in ömrü boyunca devam edebilir ve değeri hafızada sabitler.

Hayattan bir durum

Bir finansal ticaret platformu, derinlemesine iç içe geçmiş servis çağrıları boyunca talep başına piyasa verisi anlık görüntüleri depolamak için ThreadLocal kullandı. Sabit bir ThreadPoolExecutor kullanarak, uygulama üretim yükü altında her 12 saatte bir hafıza alanını gizemli bir şekilde tüketti. Hafıza dökümleri, Thread nesnelerinin ThreadLocalMap girişleriyle null anahtarlari vasıtasıyla büyük byte[] dizilerini tutmaya devam ettiğini, bu durumun servislerin bozulmasına neden olduğunu ortaya çıkardı.

Çözüm 1: Manuel try-finally hijyeni

Geliştiriciler, her giriş noktasını remove() çağıran try-finally blokları ile sarmayı denediler.

  • Artıları: Belirli temizleme; sıfır bağımlılık.
  • Eksileri: 200'den fazla endpoint üzerinde zorunlu uygulaması pratik değil; junior geliştiriciler sıklıkla bu deseni özellik geliştirme sırasında göz ardı ettiler ve bu da ara sıra sızıntılara yol açtı.

Çözüm 2: Otomatik temizleme ile thread havuzu sarıcı

Mühendisler, çalışmayı tamamladıktan sonra tüm ThreadLocal'ları yakalayıp temizlemek için Runnable görevlerini sarmayı düşündüler.

  • Artıları: Gönderim noktasında merkezi kontrol.
  • Eksileri: ThreadLocalMap halka açık olarak erişilemez; bu, JDK 17'de Java modül sistemi kısıtlamalarıyla bozulan yansıma hileleri gerektirdi.

Çözüm 3: İstek-ölçekli bağımlılık enjeksiyonu

Bağlam depolamayı Spring'in RequestScope bean'lerine taşımak ve otomatik proxy temizliği sağlamak.

  • Artıları: Çerçeve tarafından yönetilen yaşam döngüsü, manuel temizleme kodunu ortadan kaldırdı.
  • Eksileri: Statik yardımcı metotların önemli bir yeniden yapılandırılması; proxy oluşturma ve bean arama nedeniyle %15 performans aşımı.

Seçilen çözüm ve sonucu

Ekip, tüm istek-ölçekli ThreadLocal'lar için remove() çağrısının yapıldığından emin olmak için try-finally kullanan bir Servlet Filter ile hibrit bir yaklaşım seçti. Bu, mimari yeniden yapılandırma olmadan merkezileştirilmiş bir zorunluluk sağladı ve istisnalar sırasında bile birikimi önledi. Hafıza tutma %90 düştü ve zorunlu yeniden başlatma döngüsü ortadan kalktı ve %99.99 uptime SLA'sını karşıladı. Sürekli izleme, haftalarca operasyon süresince sabit hafıza kullanımını doğruladı.

Adayların sıkça atladığı noktalar

ThreadLocalMap, anahtar için neden WeakReference kullanır ama değer için neden güçlü bir referans kullanır, neden ikisini de zayıf yapmaz?

Eğer değer WeakReference aracılığıyla tutulursa, çöp toplayıcı değer nesnesini ThreadLocal anahtarı hala erişilebilirken geri alabilir. Bu, sonraki get() çağrılarının beklenmedik bir şekilde null döndürmesine neden olur ki, bu da bir thread tarafından ayarlanan bir değerin o thread'in çalışma süresi boyunca sabit kalacağı beklentisini ihlal eder. Güçlü referans, değerin kararlılığını güvence altına alırken, zayıf anahtar, girişin bozuk olarak işaretlenmesine olanak tanır; böylece ThreadLocal örneği uygulama mantığı tarafından daha fazla referans edilmediğinde.

InheritableThreadLocal, child thread'lere değerleri nasıl yayar ve bu, thread havuzu ortamlarında ne gibi benzersiz bellek sızıntısı riski doğurur?

InheritableThreadLocal, ana thread'in girişlerini child thread'in inheritableThreadLocals haritasına kopyalar. Bu yüzeysel kopyalama thread oluşturulurken gerçekleşir; bu da thread havuzlarında—thread'lerin bir kez oluşturulup tekrar kullanıldığı yerlerde—bu değerlerin yaratıldıkları sırada rastgele bir ana thread'den miras alınması anlamına gelir. Eğer o ana büyük bağlamları tutuyorsa, havuzdaki her thread bu referansları kalıcı olarak tutar ve bu, farklı kullanıcılar için görevleri işlerken farklı talepler arasında hassas verilerin sızmasına neden olabilir.

expungeStaleEntry metodunun temizleme sırasında yeniden hash işlemi yapma davranışının amacı nedir ve bozuk slot'u null yapmak haritanın invariants'ını neden bozacaktır?

ThreadLocalMap, açık adresleme ve doğrusal sorgulama kullanarak çakışmaları çözer. Bir bozuk giriş kaldırıldığında, yalnızca slot'un null yapılması durumunda sonrasındaki girişler için probe zincirini bozacaktır. expungeStaleEntry metodu, probe dizisindeki tüm sonraki girişleri yeniden hash'ler ve null slot'a ulaşana kadar doğru konumlarına taşır. Bu yeniden hash işlemesi olmadan, bu yer değiştirilmiş girişlerin arama işlemleri, yanlış olarak null döndürerek, tablodaki girişlerin mevcut olması durumunda bile burada sona erer.