JavaProgramlamaKıdemli Java Geliştirici

Eski Object.finalize() uygulamalarını java.lang.ref.Cleaner ile değiştirirken, temizlik aşamasında referans nesnesinin güvenli bir şekilde yeniden canlandırılmasını engelleyen spesifik erişilebilirlik yaşam döngüsü kısıtlaması nedir?

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

Sorunun Cevabı

Tarih, Java 9'da java.lang.ref.Cleaner'ın, tutarsız yürütme zamanlaması, performans cezaları ve yeniden canlandırma saldırıları da dahil olmak üzere güvenlik açıkları nedeniyle kullanılmayan Object.finalize() metodunun yerine geçişine dayanıyor. Temel sorun, finalize() metodunun, geçersiz kılınan metodun, çöp toplayıcısının ulaşılmaz olarak değerlendirdiği bir nesneye this referansını geri koymasına izin vermesidir. Bu durum, nesnenin çoklu yapım tekil yıkım ilkesini ihlal etmekte ve yerel kaynakların tutarsız bir durumda kalmasına yol açabilmektedir.

Çözüm, Cleaner uygulaması içinde PhantomReference anlamlarını kullanmakta: temizlik eylemi yalnızca Runnable veya temizlik eylem nesnesini alır, referansın kendisini değil ve referansın fantom erişilebilir durumda olması garanti edilmiştir—bu da onun zaten sonlandırıldığı ve yeniden canlandırılamayacağı anlamına gelmektedir—temizlik mantığının geri dönülemez bir şekilde ulaşılamaz bir nesne üzerinde işlem yapmasını sağlamakta.

public class NativeResource { private static final Cleaner cleaner = Cleaner.create(); private final long nativeHandle; public NativeResource() { nativeHandle = allocateNative(); cleaner.register(this, new CleanupAction(nativeHandle)); Reference.reachabilityFence(this); } private static class CleanupAction implements Runnable { private final long handle; CleanupAction(long handle) { this.handle = handle; } @Override public void run() { releaseNative(handle); } } private native long allocateNative(); private static native void releaseNative(long handle); }

Gerçek Hayattan Bir Durum

Ekibimiz, ImageProcessor nesnelerinin JNI aracılığıyla tahsis edilen yerel OpenCV tamponlarını sararak yüksek verimli bir görüntü işleme hattını yönetti. Başlangıçta, cvReleaseImage() çağrısını yapmak için finalize() metoduna dayanıyorduk, ancak zaman zaman tutarsız kalan yerel bellek tüketimiyle karşılaştık, bu da yerel hafızada serbest bırakma hatalarının olduğunu gösteren ara sıra segmentasyon hatalarıyla birlikteydi.

İlk düşünülen yaklaşım, "ölü" nesneleri izlemek için statik bir harita üzerinde senkronize olmak üzere bir yeniden canlandırma koruyucusu ekleyerek finalize()'ı korumaktı. Bu yaklaşım, tahmin edilemeyen gecikmelere neden oldu—temizleme işlemi, yığın baskısının başladığı andan dakikalar sonra gerçekleşebiliyordu—ve koruma mantığı, izleme haritasında "ölü" nesnelere güçlü referanslar tutarak bellek sızıntılarına neden oluyordu, ironik bir şekilde bu nesnelerin toplanmasını tamamen engelleyip yine de yeniden canlandırma koşulları yaratıyordu.

İkinci yaklaşım, yerel işaretçiyi bir lambda ile sınıfın örnek yapıcı içinde yakalayarak java.lang.ref.Cleaner'ı naif bir şekilde kullanmaktı: cleaner.register(this, () -> free(pointer)). Bu yaklaşım, sonlandırma gecikmelerini önlemekle birlikte, eğer this yapıcı sırasında kaçarsa erken toplanma riski taşıyordu ve kritik olarak, eğer lambda sadece pointer değerini kapatmak yerine ImageProcessor örneğini yanlışlıkla kapatıyorsa, bu durumda referansın çöp toplanmasını engelleyen güçlü bir referans döngüsü oluşturuyordu, fakat yine de yeniden canlandırmayı önlüyor.

Üçüncü yaklaşımı seçtik: yalnızca yerel işaretçiyi (bir long olarak) tutan ve Runnable'ı uygulayan statik iç içe CleaningAction sınıfını uygulamak. Temizleme eylemini başarıyla yerel tahsisattan hemen sonra kaydettik ve kaydın tamamlandığını garanti etmek için yapıcının sonunda Reference.reachabilityFence(this)'i açıkça çağırdık. Bu, yeniden canlandırma risklerini ve yerel sızıntıları ortadan kaldırdı ve bellek baskısı olaylarını günlük bir durumdan altı ay boyunca sıfıra indirdi.

Adayların Sıklıkla Gözden Kaçırdığı Noktalar

Neden Cleaner PhantomReference kullanıyor, WeakReference veya SoftReference yerine, ve bu, temizlik eyleminin referansın durumuna erişmesini nasıl engelliyor?

PhantomReference, çöp toplayıcının geri kazanılabilir nesneleri tanımlamasına olanak tanırken, program kodunun referansın alanlarına veya yöntemlerine erişimini engellediği için kullanılmaktadır. WeakReference'ın çöp toplama işlemi gerçekleşene kadar get() ile referansı geri almasına izin vermesine karşın, PhantomReference her zaman get()'ten null döner ve bu da temizlik eyleminin nesnenin mantıken yok edildiği varsayımıyla işlem yapmasını sağlar ve yeniden canlandırma girişimlerini veya durum denetimlerini önleyerek sonradan gelme koşullarını ihlal etmesini engeller.

Object.finalize() bağlamında "yeniden canlandırma" saldırısı nedir ve neden tür sistemi güvenlik garantilerini ihlal ediyor?

Yeniden canlandırma, finalize() metodunun this referansını statik bir alana veya canlı nesne grafiğine kaydetmesiyle gerçekleşir; bu da nesnenin çöp toplayıcı tarafından geri kazanım için işaretlendikten sonra ulaşılabilir hale gelmesini sağlar. Bu, bir nesnenin yapıcısının tam olarak bir kez çalıştırılması ve sonlandırıcısının en fazla bir kez çalıştırılması koşulunu ihlal eder ve kötü niyetli veya hatalı kodun, yerel kaynakların serbest bırakıldığı fakat Java alanlarının hala erişilebilir olduğu parçalı bir yok edilme durumunda bir nesneyi gözlemlemesine izin vererek serbest bırakma sonrası güvenlik açıklarına ve tutarsız nesne davranışlarına yol açar.

Reference.reachabilityFence, JVM'nin anında derleme optimizasyonlarıyla nasıl etkileşir ve Cleaner kayıtları ile ilgili olarak ne zaman kesinlikle gerekli bir şekilde kullanılmalıdır?

Reference.reachabilityFence, kritik bölüm tamamlanmadan nesne referanslarının yeniden düzenlenmesini veya ortadan kaldırılmasını önleyen bir derleyici engeli gibi davranır ve özelikle "erken yayımlama" durumunu engeller, burada bir nesne erişilemez hale gelirken yapıcı hala çalışmaktadır. Bu, nesneleri yapım sırasında Cleaner ile kaydederken kesinlikle gereklidir çünkü olmadan, JVM, yerel kaynak tahsisi yapıldıktan sonra this'in daha fazla ihtiyaç duymadığına karar verebilir ama kayıt çağrısından önce bu durumda temizleyicinin çalışmasına ve kaynağı serbest bırakmasına izin verir; oysa yapıcı nesneyi başlatmaya devam eder ve bu da kaynakların bozulmasına yol açabilir.