Garbage Collector’ı Çöp Ettiler!

3 minute read

Oracle aldıktan sonra Java’nın geleceği ile alakalı kaygılarım vardı. Kendi kendime: “Bu paragöz şirket Java’yı aldıktan, James (Gosling) baba’yı da şutladıktan sonra Java’yı bozar” demiştim. Çok geçmeden GC’den ötürü durup dururken “core-dump”lar atmaya başlayınca endişemde haklı olduğum kanısına vardım.

Hikayemiz şöyle gelişti. Bizim bir uygulamamız var. Bunu integrasyon testlerini yapmak için birkaç makineye kurduk. Daha testlere başlamadan “Houston! Bir sorun var!” diye mesajlar almaya başladık. Baktık tabi duruma: Üzerinde yük yok, makine sağlam bir makine, jvm’in en son versiyonu (bi sürü “bug fix” yapılmış olanı), native lib’ler falan hepsi tastamam, ama uygulamanın biri bir-iki saat çalışıp “core” atıyor. Cluster’dan mı lan yoksa diye işkilleniyoruz. JVM aklımıza bile gelmiyor.

Hemen core dump’ları başladık incelemeye. “GCThread”, “GCConcThread” yani ismiyle müsemma Garbage Collector thread’leri yamulmuş. O kadar zamandır Java ile uğraşıyorum. GC ile JVM’in core attığını nadir görmüşümdür. Daha önce de yine başka bir uygulamada GC ile ilgili core’ları yemiştik. jvm versiyonunu yükselttik, GC tuning yaptık, sonra jvm versiyonunu alçalttık falan sorunu bir şekilde çözmüştük.

Tabi, hemen sorun ile ilgili bir google danışmak gerekti. Google da bizi jvm bug veritabanına yönlendirdi. Bizim gibi başkaları da çeşitli jvm versiyonlarında benzer sorunları yaşamışlar. Her bug için farlkı work around’lar var. E haliyle, niyet ettik niyet eyledik work around’ları uygulamaya. Yalnız şöyle bir durum oldu ki bir work around’u uygulayınca bir süre sonra yine core attı. Onu aratıyorsun başka bir çözüm, o çözümü uyguluyorsun yine patlıyor! Böyle günlerce uğraştık.

Her seferinde yeni bir work around, biraz GC tuning, GC’yi gözleme (jvisual vm + gc viewer plugin) yapıp ne oluyor, nasıl oluyor anlamaya çalıştık. Sonunda sorun çözüldü artık core atmıyor. Ama neden atmıyor, ben bilmiyorum henüz. Ya bam telinden yakaladık da uygun GC parametrelerini tutturduk (ki bu olduğuna inanmıyorum) ya da kendiliğinden sorun halloldu veya sizi çok uğraştırdım siz biraz takılın sonra yine başınızı ağrıtırım dedi ve bir gün geri gelecek.

Birkaç örnek vereyim nelerle uğraştık:

Multiple JVM crashes seen with 1.6.0_10 through early access of 1.6.0_14 – possibly related to GC

“core dump”‘lardan biri buraya kadar getirmişti. Çözüm önerisi:

-XX:-ReduceFieldZeroing -XX:-ReduceInitialCardMarks -XX:-ReduceBulkZeroing

parametrelerini kullanın idi. Kullandık yine patladı.

CMS: reference processing crash if ParallelCMSThreads & ParallelGCThreads

Hakkaten bizde de CMS thread sayısı, gc thread’lerden çoktu. Çözüm önerisi:

always use ParallelCMSThreads <= ParallelGCThreads (if modifying them via the command line).

Çözümü uyguladık ama yine patladı.

Netice itibariyle bugünkü yorumum şudur ki: “GC artık çöp olmuştur!”

Hadi diyelim benim durumum bana özel. Her koyun kendi bacağından asılır. Ya da ne biliyim benim derdim elalemi niye gersin. O zaman şuna ne diyeceksiniz: GC Tuning denen bir uzmanlık alanı veya sanat çıktı. Yani memory’yi temizleme işi araç olmaktan çıktı amaç haline geldi! Burada bir enayilik var. Onlarca collector, yüzlerce GC parametresi var. Bu parametreler ile ilgili dokümantasyon eksiği var. Biri ötekini eziyor, diğeri berikiyle yapmak istediğin şeyi etkisiz hale getiriyor, vs. Bu kadar collector ve parametre fazla. Böyle olmaması gerekir. İşin bir diğer ram’ler ucuzladı. Ama jvm heap’inin 4-6 gb üzerinde vermeye başlayınca uzun süren “durdurun dünyayı inecek var!” durumları yaşanıyor ve bu süre bizim işler için kabul edilebilir değil!

Mesela “young gen.”‘i paralel olarak temizlemek için GC’nin harcadığı zaman genellikle kabul edilir oluyor. Buna güvenip diyelim boyutunu artırınca bu sefer young’ın temizlenmesinin uzadığını görüyorsunuz. Uygulamanın yarattığı objeler genellik orta ömürlü ise, young’ın boyunu makul bir seviyede artırıp, survivor space’lerin büyüklüğünü ayarlayıp, tenuring threshold’u max değerine getirip, old’a az ideal de hiç obje atmak için bir tuning yapsanız bile old’a atılmasına ve hatta gc’nin paralel’den seriye düşmesine kesinlikle engel olamıyorsunuz.

CMS “durdurun dünyayı” sendromunun en az olması için şu anda en çok kullanılan toplayıcı. Ama o da compaction yapmadığı yani ölü nesneleri işaretleyip geçtiği, old gen alanında boş alanları bir araya getirmediği için fragmantasyon çok oluyor ve bu mem alloc. performansını etkiliyor. Bunun gibi birçok dikkat edilmesi gereken ve bilinmesi gereken şey var GC ile ilgili.

Netice itibariyle olması gereken şudur:

java -Xmx30g <main-class> 

gibi.

Yani uygulamamı max. 30 gb heap ile çalıştır. GC’nin detaylarıyla beni uğraştırma! Madem otomatik mem management yapıyorum diyorsun benden yüz tane hint isteme. Hadi bakiym!