來自RedHat的性能和OpenJDK開發(fā)者Aleksey Shipil v,提交了一份新的JEP草案,其內(nèi)容為創(chuàng)建一個(gè)無操作垃圾回收器:一種實(shí)際上不進(jìn)行實(shí)際內(nèi)存回收的GC方式。該回收器旨在幫助JVM實(shí)現(xiàn)者和研究者,以及少部分無需垃圾回收的超高性能應(yīng)用程序。如果這項(xiàng)JEP繼續(xù)推進(jìn),新的GC方式將會(huì)和現(xiàn)有GC方式一起存在,并且通過顯式激活方式使用。
垃圾回收和Java性能向來都是復(fù)雜的話題,為了能夠更清晰的說明,InfoQ聯(lián)系了Java Champions成員、性能專家Martijn Verburg和Kirk Pepperdine。同時(shí),我們也聯(lián)系了無GC Log4j轉(zhuǎn)型領(lǐng)導(dǎo)者Remko Popma,作為如何實(shí)現(xiàn)這一GC方式的代表人物之一。Martijn和Remko確認(rèn),在他們看來,無操作GC——既Epsilon GC的首要受益者是GC的開發(fā)者和性能研究人員。Epsilon GC可以作為度量其他垃圾回收器性能的對(duì)照組。一個(gè)簡單的例子,我們可以將一個(gè)應(yīng)用程序的GC方式設(shè)置成無操作GC以減少GC開銷(以避免內(nèi)存分配和其他不可控因素)。如果相同應(yīng)用程序在相同情況下運(yùn)行,只是修改了不同GC算法配置,性能上的不同就可以證明該種GC方式在性能上的開銷。這將會(huì)幫助GC開發(fā)者和性能研究人員在更加孤立的方式理解垃圾回收行為。
“我認(rèn)為這實(shí)際上是對(duì)精確度量JVM各個(gè)部分跨出的一大步。(例如現(xiàn)存JIT的C1/C2編譯器,可能會(huì)被轉(zhuǎn)移到Graal等)。它將會(huì)為JVM增加額外的壽命。”——Martijn Verburg
另一方面,一些對(duì)性能嚴(yán)格要求的應(yīng)用程序也將會(huì)從Epsilon GC受益。有非常少見的應(yīng)用程序和庫,例如前文提到的Log4j,已經(jīng)被設(shè)計(jì)成不產(chǎn)生垃圾,因此他們無需使用垃圾回收器。對(duì)于這種類型應(yīng)用程序,移除垃圾回收器可以提升性能。然而,如Remko強(qiáng)調(diào)的,構(gòu)建一個(gè)可以運(yùn)行在Epsilon GC的庫,“將花費(fèi)大量的工程努力,以確保應(yīng)用程序的內(nèi)存被自己管理,不會(huì)耗盡”。但是這樣也必須進(jìn)行風(fēng)險(xiǎn)和性能評(píng)估,以確認(rèn)選擇無操作GC的收益和實(shí)現(xiàn)無垃圾狀態(tài)做的努力是否相同。
應(yīng)用程序如何可以不產(chǎn)生垃圾看上去很難想象,而且這個(gè)話題已經(jīng)復(fù)雜到超出本文討論的范疇,但是如果考慮以下幾個(gè)方面可能會(huì)容易理解:
JVM將內(nèi)存分成兩個(gè)部分來管理:堆和棧。這就是為什么當(dāng)缺少內(nèi)存時(shí)會(huì)有兩個(gè)不同的錯(cuò)誤(OutOfMemoryError和StackOverflowError)。棧內(nèi)存只能被當(dāng)前線程和當(dāng)前執(zhí)行的方法訪問,因此,當(dāng)線程離開當(dāng)前執(zhí)行的方法,這塊內(nèi)存會(huì)被自動(dòng)釋放,而無需額外垃圾回收器。然而,堆內(nèi)存可以被整個(gè)應(yīng)用程序在任何時(shí)刻訪問,這意味著獨(dú)立的垃圾回收器需要區(qū)分內(nèi)存塊什么時(shí)候才不被使用,可以被回收。基本數(shù)據(jù)類型內(nèi)存總是在棧上分配,因此這對(duì)垃圾回收器沒有壓力。如果代碼中基本上都使用基本類型,那么垃圾回收器處理的對(duì)象就少了。不產(chǎn)生垃圾不等于不創(chuàng)建對(duì)象,如果對(duì)象創(chuàng)建滿足以下幾個(gè)條件,仍然可以在創(chuàng)建對(duì)象之后不需要垃圾回收器:應(yīng)用程序或者庫在初始化的時(shí)候生成有限個(gè)數(shù)的對(duì)象,然后不斷復(fù)用這些對(duì)象。但是這需要依賴開發(fā)者非常熟悉應(yīng)用程序的內(nèi)存占用。有的時(shí)候編譯器可以發(fā)現(xiàn)一些特定對(duì)象不會(huì)在方法外使用,這被稱為逃逸分析。當(dāng)確認(rèn)對(duì)象生命周期不會(huì)超過方法,其內(nèi)存可以分配到棧而非堆。因此,這些對(duì)象占用的內(nèi)存會(huì)在當(dāng)前方法結(jié)束的時(shí)候自動(dòng)消除。雖然去除垃圾回收可能實(shí)現(xiàn),Kirk指出這樣編寫代碼會(huì)非常不自然,并且會(huì)失去Java提供的很多特性。Martijn也同時(shí)指出,內(nèi)存管理恰恰也是Java能夠在業(yè)界成功的一個(gè)主要原因。另外,我們需要牢記,雖然被叫做垃圾回收器,垃圾回收器的任務(wù)不僅僅是回收無用的內(nèi)存,還包括分配新的內(nèi)存塊,Epsilon GC仍然需要實(shí)現(xiàn)該功能。這也正是Kirk的論據(jù),至少理論上,使用Epsilon GC和將其他垃圾回收算法配置成實(shí)際不進(jìn)行垃圾回收之后,在性能上沒有顯著差異。
然而,盡管考慮到所有這些注意事項(xiàng),Kirk和Martijn證實(shí)Epsilon GC還是會(huì)對(duì)一小部分受眾非常有用,但是根據(jù)Kirk所說,這些受眾將非常少,并懷疑它的實(shí)際用處。絕大部分應(yīng)用程序需要在一些時(shí)刻回收內(nèi)存,因此需要一個(gè)有實(shí)際功能的垃圾回收器。
“合理的GC停頓時(shí)間對(duì)于大部分應(yīng)用程序來說不是問題,因此為什么要為了一個(gè)可能存疑的性能優(yōu)勢(shì)而放棄Java的所有好處?”——Kirk Pepperdine
Kirk同時(shí)提到,新增的每一項(xiàng)新特性都會(huì)增加OpenJDK的維護(hù)成本,因此OpenJDK開發(fā)人員在添加新特性時(shí)必須有全局考慮。Oracle一直在減少垃圾回收器的數(shù)量,以降低維護(hù)成本,因此為一小部分用戶添加一個(gè)垃圾回收器可能不是一個(gè)合理的投資。Aleksey在起草JEP草案的時(shí)候,看上去考慮到了這些點(diǎn),通過JEP草案內(nèi)容和已經(jīng)提供的原型來看,初步分析開銷可能不大。事實(shí)上,Kirk和Martijn同時(shí)指出,考慮到Aleksey的經(jīng)驗(yàn),應(yīng)該對(duì)他所領(lǐng)導(dǎo)的這個(gè)倡議保持樂觀。
另一方面,Kirk也強(qiáng)調(diào),雖然OpenJDK是JVM的一個(gè)參考實(shí)現(xiàn),但是對(duì)垃圾回收器沒有強(qiáng)制性要求,這意味著只要完全兼容Java規(guī)范,分發(fā)者可以有他們自己的垃圾回收算法實(shí)現(xiàn)。這可能導(dǎo)致公眾意見的分歧:其中一部分人可能認(rèn)為為特定市場實(shí)現(xiàn)的算法可能更適合商業(yè)版本的JVM,另一些人可能會(huì)認(rèn)為這對(duì)OpenJDK是一個(gè)非常有用的補(bǔ)充。
Remko還建議當(dāng)性能變得至關(guān)重要時(shí),應(yīng)該考慮使用商業(yè)版JVM,因?yàn)樵谫徺I許可證上的開銷會(huì)比讓工程師選擇和調(diào)優(yōu)特定的GC算法要小。但是,即使有些人只選擇使用OpenJDK,Remko和Martijn都提到當(dāng)前正在開發(fā)中的Shenandoah GC,其目的是針對(duì)非常大的堆內(nèi)存(100GB甚至更大)也只有超低停頓時(shí)間。無論如何,專家們的共識(shí)是,當(dāng)應(yīng)用程序遇到性能問題時(shí),一個(gè)經(jīng)過謹(jǐn)慎選擇的GC算法總是優(yōu)于完全沒有GC。
該提案仍然處于初級(jí)階段,在稱為正式JEP之前仍需要進(jìn)行審查和潤色。讓它稱為正式稿之后,將會(huì)被加入到JVM版本中。雖然目前我們只能推測(cè)最終會(huì)添加到什么版本JVM中,Martijn認(rèn)為有理由期待Epsilon GC將會(huì)加入到Java 10或者11。如果要說有什么好處的話,Epsilon GC至少能夠幫助理解GC的接口,有助于成就一個(gè)更加模塊化的JVM。
查看英文原文:The Last Frontier in Java Performance: Remove the Garbage Collector