Java'da değişkenler için final kullanmak çöp toplamayı iyileştirir mi?


86

Bugün meslektaşlarım ve ben final, çöp toplamayı iyileştirmek için anahtar kelimenin Java'da kullanımı hakkında bir tartışmamız var .

Örneğin, aşağıdaki gibi bir yöntem yazarsanız:

public Double doCalc(final Double value)
{
   final Double maxWeight = 1000.0;
   final Double totalWeight = maxWeight * value;
   return totalWeight;  
}

Yöntemdeki değişkenlerin bildirilmesi final, çöp toplama işleminin, yöntemden çıktıktan sonra yöntemdeki kullanılmayan değişkenlerden belleği temizlemesine yardımcı olur.

Bu doğru mu?


orada iki şey aslında, burada. 1) bir yöntem yerel alanına ve bir örneğe yazdığınızda . Bir örneğe yazdığınızda bir fayda olabilir .
Eugene

Yanıtlar:


86

İşte biraz farklı bir örnek, son değer türü yerel değişkenler yerine son başvuru türü alanları olan bir örnek:

public class MyClass {

   public final MyOtherObject obj;

}

MyClass'ın bir örneğini her oluşturduğunuzda, bir MyOtherObject örneğine giden bir referans oluşturacaksınız ve GC canlı nesneleri aramak için bu bağlantıyı takip etmek zorunda kalacak.

JVM, GC "kök" konumlarındaki tüm canlı referansları (mevcut çağrı yığınındaki tüm nesneler gibi) incelemek zorunda olan bir mark süpürme GC algoritması kullanır. Her canlı nesne, canlı olarak "işaretlenir" ve canlı bir nesne tarafından atıfta bulunulan herhangi bir nesne de canlı olarak işaretlenir.

İşaretleme aşamasının tamamlanmasından sonra, GC, tüm işaretlenmemiş nesneler için belleği serbest bırakarak (ve kalan canlı nesneler için belleği sıkıştırarak) yığın içinde gezinir.

Ayrıca, Java yığın belleğinin "genç nesil" ve "eski nesil" olarak bölündüğünü bilmek önemlidir. Tüm nesneler başlangıçta genç nesilde tahsis edilir (bazen "kreş" olarak anılır). Çoğu nesne kısa ömürlü olduğundan, GC, genç nesilden yeni çöpleri kurtarmak konusunda daha agresiftir. Bir nesne genç neslin bir toplama döngüsünde hayatta kalırsa, daha az sıklıkta işlenen eski nesle (bazen "görevli nesil" olarak anılır) taşınır.

Öyleyse, aklımın ucunda, "hayır, 'son' değiştirici, GC'nin iş yükünü azaltmasına yardımcı olmuyor" diyeceğim.

Bana göre, Java'da bellek yönetiminizi optimize etmek için en iyi strateji, sahte referansları olabildiğince çabuk ortadan kaldırmaktır. Bunu, kullanmayı bitirir bitirmez bir nesne referansına "null" atayarak yapabilirsiniz.

Ya da daha iyisi, her bildirim kapsamının boyutunu en aza indirin. Örneğin, 1000 satırlık bir yöntemin başlangıcında bir nesne bildirirseniz ve nesne bu yöntemin kapsamının (son kapanış küme ayracı) kapanmasına kadar hayatta kalırsa, nesne gerçekte olduğundan çok daha uzun süre canlı kalabilir. gerekli.

Yalnızca bir düzine kadar kod satırı içeren küçük yöntemler kullanırsanız, bu yöntem içinde bildirilen nesneler daha hızlı kapsam dışına çıkacak ve GC işinin çoğunu çok daha verimli bir şekilde yapabilecektir. genç nesil. Kesinlikle gerekmedikçe nesnelerin eski nesle taşınmasını istemezsiniz.


Düşünce için yiyecek. Her zaman satır içi kodun daha hızlı olduğunu düşünmüşümdür, ancak jvm bellek yetersiz kalırsa o zaman da yavaşlayacaktır. hmmmmm ...
WolfmanDragon

1
Burada sadece tahmin ediyorum ... ancak JIT derleyicisinin orta düzeyde performans artışları için son ilkel değerleri (nesneleri değil) satır içi yapabileceğini varsayıyorum. Öte yandan kod satır içi, önemli optimizasyonlar üretebilir, ancak son değişkenlerle ilgisi yoktur.
benjismith

2
Zaten oluşturulmuş bir nihai nesneye sıfır atamak mümkün olmazdı, sonra belki yardım yerine nihai , işleri zorlaştırabilir
Hernán Eche

1
Diğer sınıf yöntemleriyle ilgili olmayabilecek birkaç özel yönteme bölmek yerine, büyük bir yöntemde kapsamı sınırlamak için {} kullanılabilir.
mmm

Bellek çöp toplamasını geliştirmek ve bilgi bağlarını azaltmak mümkün olduğunda alanlar yerine yerel değişkenler de kullanabilirsiniz.
sivi

37

Yerel bir değişkenin bildirilmesi finalçöp toplamayı etkilemez, bu sadece değişkeni değiştiremeyeceğiniz anlamına gelir. Yukarıdaki örneğiniz totalWeight, işaretlenmiş değişkeni değiştirirken derlenmemelidir final. Öte yandan, bir ilkel bildirmek ( doubleyerine Double) final, bu değişkenin çağıran koda satır içi olmasına izin verir, böylece biraz bellek ve performans iyileştirmesine neden olabilir. Bu, public static final Stringsbir sınıfta bir sayıya sahip olduğunuzda kullanılır .

Genel olarak, derleyici ve çalışma zamanı yapabildiği yeri optimize eder. En iyisi kodu uygun şekilde yazmak ve fazla zorlayıcı olmaya çalışmamaktır. finalDeğişkenin değiştirilmesini istemediğinizde kullanın . Herhangi bir kolay optimizasyonun derleyici tarafından gerçekleştirileceğini varsayın ve performans veya bellek kullanımı konusunda endişeleriniz varsa, gerçek sorunu belirlemek için bir profilleyici kullanın.


26

Hayır, kesinlikle doğru değil.

finalBunun sabit anlamına gelmediğini unutmayın, sadece referansı değiştiremeyeceğiniz anlamına gelir.

final MyObject o = new MyObject();
o.setValue("foo"); // Works just fine
o = new MyObject(); // Doesn't work.

JVM'nin referansı hiçbir zaman değiştirmek zorunda kalmayacağı bilgisine dayanan bazı küçük optimizasyonlar olabilir (örneğin, değişip değişmediğini kontrol etmemek gibi), ancak endişelenmemek için çok küçük olacaktır.

Final Derleyici optimizasyonu olarak değil, geliştirici için yararlı meta veri olarak düşünülmelidir.


17

Temizlenecek bazı noktalar:

  • Referansın geçersiz kılınması GC'ye yardımcı olmamalıdır. Eğer öyleyse, değişkenlerinizin kapsamının fazla olduğunu gösterir. Bir istisna, nesne kayırmacılık durumudur.

  • Java'da henüz yığın üzerinde tahsis yoktur.

  • Bir değişken final bildirmek, (normal koşullar altında) bu değişkene yeni bir değer atayamayacağınız anlamına gelir. Final kapsam hakkında hiçbir şey söylemediğinden, GC üzerindeki etkisi hakkında hiçbir şey söylemiyor.


Java'da yığın üzerinde tahsis (ilkellerin ve öbekteki nesnelere referansların) vardır: stackoverflow.com/a/8061692/32453 Java, anonim bir sınıf / lambda ile aynı kapanışta bulunan nesnelerin de nihai olmasını gerektirir, ancak döner Bu sadece "gerekli bellek / çerçeve" yi azaltmak / kafa karışıklığını azaltmak içindir, dolayısıyla toplanmasıyla ilgisi yoktur ...
rogerdpack

11

Pekala, bu durumda "son" değiştiricinin kullanımını veya bunun GC üzerindeki etkisini bilmiyorum.

Ama olabilir bunu söylemek: kutulu kullanımınız ilkel (örn yerine çift çift) yığın yerine yığın bu nesneleri tahsis edecek ve GC temizlemek zorunda olacağı gereksiz çöp üretecek yerine değer verir.

Kutulu ilkelleri yalnızca mevcut bir API tarafından gerektiğinde veya null yapılabilir ilkelere ihtiyacım olduğunda kullanıyorum.


1
Haklısın. Sorumu açıklamak için sadece hızlı bir örneğe ihtiyacım vardı.
Goran Martinic

5

Son değişkenler ilk atamadan sonra değiştirilemez (derleyici tarafından zorlanır).

Bu, çöp toplama işleminin davranışını değiştirmez . Tek sorun, bu değişkenler artık kullanılmadığında boş bırakılamaz (bu, bellek kısıtlı durumlarda çöp toplamaya yardımcı olabilir).

Final'in derleyicinin neyi optimize edeceği konusunda varsayımlar yapmasına izin verdiğini bilmelisiniz. Satır içi kod ve ulaşılamayacağı bilinen kodu içermeyen.

final boolean debug = false;

......

if (debug) {
  System.out.println("DEBUG INFO!");
}

Println, bayt koduna dahil edilmeyecektir.


@Eugene Güvenlik yöneticinize ve derleyicinin değişkeni satır içi yapıp yapmadığına bağlıdır.
Thorbjørn Ravn Andersen

doğru, sadece bilgiçlik yapıyordum; daha fazlası yok; ayrıca bir cevap da verdi
Eugene

4

Nesilsel çöp toplayıcılarla ilgili çok iyi bilinmeyen bir köşe durumu yok. (Kısa bir açıklama için benjismith'in cevabını okuyun için, daha derin bir kavrayış için makaleleri okuyun)

Kuşaksal GC'lerdeki fikir, çoğu zaman sadece genç nesillerin dikkate alınması gerektiğidir. Kök konumu referanslar için taranır ve ardından genç nesil nesneler taranır. Bu daha sık taramalar sırasında eski nesildeki hiçbir nesne kontrol edilmez.

Şimdi sorun, bir nesnenin daha genç nesnelere referans vermesine izin verilmemesinden kaynaklanıyor. Uzun ömürlü (eski nesil) bir nesne yeni bir nesneye referans aldığında, bu referans çöp toplayıcı tarafından açıkça izlenmelidir ( hotspot JVM toplayıcısıyla ilgili IBM makalesine bakın) ), bu da aslında GC performansını etkiler.

Eski bir nesnenin daha genç bir nesneye atıfta bulunamamasının nedeni, eski nesnenin küçük koleksiyonlarda kontrol edilmediği için, nesneye tek atıf eski nesnede tutulursa, işaretlenmeyeceği ve yanlış olacağıdır. süpürme aşamasında serbest bırakıldı.

Elbette, birçok kişinin işaret ettiği gibi, son anahtar kelime çöp toplayıcısını gerçekten etkilemez, ancak bu nesne küçük koleksiyonlardan sağ çıkarsa ve onu daha eski yığın haline getirirse referansın asla daha genç bir nesneye dönüşmeyeceğini garanti eder.

Nesne:

Atık toplama konusunda IBM: geçmiş , etkin nokta JVM'si ve performans . Bunlar, 2003 / 04'e dayandığından, artık tam olarak geçerli olmayabilir, ancak GC'ler hakkında okunması kolay bir fikir verirler.

Tuning çöp toplama üzerinde Sun


3

GC ulaşılamayan referanslara göre hareket eder. Bunun, yalnızca tek seferlik bir atama iddiası olan "nihai" ile ilgisi yoktur. Bazı VM'lerin GC'sinin "final" i kullanması mümkün müdür? Nasıl veya neden olduğunu anlamıyorum.


3

finalyerel değişkenler ve parametreler, üretilen sınıf dosyalarında hiçbir fark yaratmaz, bu nedenle çalışma zamanı performansını etkileyemez. Bir sınıfın alt sınıfı yoksa, HotSpot bu sınıfa yine de sonmuş gibi davranır (bu varsayımı bozan bir sınıf yüklenirse daha sonra geri alabilir). finalYöntemlerin sınıflarla aynı olduğuna inanıyorum . finalstatik alan, değişkenin bir "derleme zamanı sabiti" olarak yorumlanmasına ve optimizasyonun javac tarafından bu temelde yapılmasına izin verebilir. finalalanlar, JVM'ye ilişkilerden önce olanları görmezden gelme özgürlüğü verir .


2

Varsayımlarda dolaşan pek çok yanıt var gibi görünüyor. Gerçek şu ki, bayt kodu seviyesinde yerel değişkenler için son bir değiştirici yoktur. Sanal makine, yerel değişkenlerinizin nihai olarak tanımlandığını asla bilemez.

Sorunuzun cevabı kesin bir hayır.


Bu doğru olabilir, ancak derleyici yine de veri akışı analizi sırasında son bilgileri kullanabilir.

@WernerVanBelle, derleyici zaten bir değişkenin yalnızca bir kez ayarlandığını biliyor . Bir değişkenin boş olabileceğini, kullanımdan önce başlatılmadığını vb. Bilmek için zaten veri akışı analizi yapmak zorundadır. Bu nedenle, yerel finaller derleyiciye herhangi bir yeni bilgi sunmaz.
Matt Quigley

Öyle değil. Veri akışı analizi pek çok şeyi çıkarabilir, ancak bir blok içinde yerel bir değişken ayarlayacak veya ayarlamayacak tam bir programa sahip olmak mümkündür. Derleyici, değişkenin yazılıp yazılmayacağını ve sabit olup olmayacağını önceden bilemez. Bu nedenle derleyici, final anahtar sözcüğü olmadan, bir değişkenin nihai olup olmadığını garanti edemez.

@WernerVanBelle Gerçekten meraklandım, bir örnek verebilir misiniz? Derleyicinin bilmediği son olmayan bir değişkene nasıl son bir atama yapılabileceğini anlamıyorum. Derleyici, başlatılmamış bir değişkeniniz varsa, onu kullanmanıza izin vermeyeceğini bilir. Bir döngünün içinde son bir değişken atamaya çalışırsanız, derleyici size izin vermez. Bir değişkenin nihai olarak bildirilebildiği, ancak DEĞİL olduğu ve derleyicinin son olduğunu garanti edemediği bir örnek nedir? Herhangi bir örneğin ilk etapta nihai olarak ilan edilemeyecek bir değişken olacağından şüpheleniyorum.
Matt Quigley

Yorumunuzu yeniden okuduktan sonra, örneğinizin koşullu bir blok içinde başlatılan bir değişken olduğunu görüyorum. Tüm koşul yolları değişkeni bir kez başlatmadıkça, bu değişkenler ilk etapta son olamaz. Dolayısıyla, derleyici bu tür bildirimleri bilir - bu, iki farklı yerde başlatılmış bir son değişkeni derlemenize izin verir (düşünün final int x; if (cond) x=1; else x=2;). Son anahtar sözcüğü olmadan derleyici, bu nedenle bir değişkenin nihai olup olmadığını garanti edebilir.
Matt Quigley

1

Alt sınıflarda tüm yöntem ve değişkenler varsayılan olarak geçersiz kılınabilir. Alt sınıfları, üst sınıfın üyelerini geçersiz kılmaktan kurtarmak istiyorsak, final anahtar sözcüğünü kullanarak bunları nihai olarak ilan edebiliriz. Örneğin, final int a=10; final void display(){......} bir yöntemi final yapmak, üst sınıfta tanımlanan işlevselliğin hiçbir şekilde değiştirilmemesini sağlar. Benzer şekilde, son değişkenin değeri de asla değiştirilemez. Son değişkenler, sınıf değişkenleri gibi davranır.


1

Kesin hakkında konuşan örneği , tarlalar final olabilir belirli bir GC olduğunu yararlanmaya istiyorsa biraz performansını artırmak. Bir eşzamanlılık GCgerçekleştiğinde (bu, GC devam ederken uygulamanızın hala çalıştığı anlamına gelir ), daha geniş bir açıklama için buna bakın , GC'ler yazma ve / veya okumalar yapılırken belirli engelleri kullanmak zorundadır. Size verdiğim bağlantı bunu oldukça fazla açıklıyor, ancak gerçekten kısaltmak için: a GCeşzamanlı bir çalışma yaptığında, tüm okuma ve yığına yazmalar (bu GC devam ederken) "yakalanır" ve daha sonra uygulanır; böylece eşzamanlı GC aşaması işini bitirebilir.

Örneğin finalalanlar, değiştirilemedikleri için (yansıma yapılmadıkça), bu engeller göz ardı edilebilir. Ve bu sadece saf bir teori değil.

Shenandoah GConlara sahip pratikte (gerçi uzun süre değil ), ve örneğin, yapabilirsiniz:

-XX:+UnlockExperimentalVMOptions  
-XX:+UseShenandoahGC  
-XX:+ShenandoahOptimizeInstanceFinals

Ve GC algoritmasında onu biraz daha hızlı hale getirecek optimizasyonlar olacak. Bunun nedeni final, hiç kimse onları değiştirmemesi gerektiğinden, engellemeyi engelleyen hiçbir engel olmayacak olmasıdır . Yansıma veya JNI yoluyla bile.


0

Aklıma gelen tek şey, derleyicinin son değişkenleri optimize edebilmesi ve bunları sabitler olarak kodun içine yerleştirebilmesidir, böylece tahsis edilmiş bellek kalmazsınız.


0

kesinlikle, nesnenin ömrünü kısaltarak bellek yönetiminden büyük fayda sağladığı sürece, son zamanlarda bir testte örnek değişkenleri olan dışa aktarma işlevini ve yöntem düzeyinde yerel değişkene sahip başka bir testi inceledik. yük testi sırasında, JVM ilk testte bellek hatasını atar ve JVM durdurulur. ancak ikinci testte, daha iyi bellek yönetimi sayesinde raporu başarılı bir şekilde alabildi.


0

Yerel değişkenleri nihai olarak bildirmeyi tercih ettiğim tek zaman şu durumlarda:

  • Ben var onlara bazı anonim sınıfla paylaşılabilir bu son öylesine yapmak için (örneğin: cini parçacığı oluşturma ve yöntemi çevreleyen bazı değer erişmesine izin)

  • Ben istiyorum , onlara son yapmak (örneğin: / yanlışlıkla geçersiz kılınan almaz gereken bazı değer)

Hızlı çöp toplamaya yardımcı olurlar mı?
AFAIK bir nesne, kendisine sıfır güçlü referansa sahipse GC koleksiyonunun bir adayı haline gelir ve bu durumda da hemen çöp toplanacağına dair hiçbir garanti yoktur. Genel olarak, güçlü bir referansın kapsam dışına çıktığında veya kullanıcının bunu açıkça boş referansa yeniden atadığında ortadan kalktığı söylenir, bu nedenle son olarak bildirilmesi, referansın yöntem var olana kadar var olmaya devam edeceği anlamına gelir (kapsamı açıkça daraltılmadıkça) belirli bir iç blok {}) çünkü son değişkenleri yeniden atayamazsınız (yani null'a yeniden atayamazsınız). Bu yüzden, atık Toplama 'final'in istenmeyen bir olası gecikmeye yol açabileceğini düşünüyorum , bu nedenle, GC için ne zaman aday olacaklarını kontrol eden kapsamı tanımlarken biraz dikkatli olunmalıdır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.