關(guān)于Java垃圾回收被誤解的7件事
對(duì)Java垃圾回收最大的誤解是什么?它實(shí)際又是什么樣的呢?
當(dāng) 我還是小孩的時(shí)候,父母常說如果你不好好學(xué)習(xí),就只能去掃大街了。但他們不知道的是,清理垃圾實(shí)際上是很棒的一件事??赡苓@也是即使在Java的世界中, 同樣有很多開發(fā)者對(duì)GC算法產(chǎn)生誤解的原因——包括它們?cè)鯓庸ぷ鳌C是如何影響程序運(yùn)行和你能對(duì)它做些什么。因此我們找到了Java性能調(diào)優(yōu)專家Haim Yadid,并把名為Java performance tuning guide的文章發(fā)表在Takipi的博客上。
帶著對(duì)性能調(diào)優(yōu)指南濃厚的興趣,我們決定在這篇后續(xù)的博文中收集一些關(guān)于垃圾回收的流行觀點(diǎn),并且指出為什么它們完全是錯(cuò)誤的。
來看看前7名:
1. 只有一個(gè)垃圾回收器
不,并且4也是錯(cuò)誤的答案。HotSpot JVM一共有4個(gè)垃圾回收器:Serial, Parallel / Throughput. CMS, and the new kid on the block G1。別急,另外還有一些非標(biāo)準(zhǔn)的垃圾回收器和更大膽的實(shí)現(xiàn),比如Shenandoah或 者其他JVM使用的回收器(C4——Azul開發(fā)的無停頓回收器)。HotSpot默認(rèn)使用Parallel / Throughput回收器,但它常常不是你運(yùn)行程序的最佳選擇。比如CMS和G1會(huì)使GC停頓(GC pause)發(fā)生的頻率降低,但是對(duì)于每次停頓所花費(fèi)的時(shí)間,很可能比Parallel回收器更長(zhǎng)。另一方面來說,在使用相同大小堆內(nèi)存的情況 下,Parallel回收器能帶來更高的吞吐量。
結(jié)論:根據(jù)你的需求(可接受的GC停頓頻率和持續(xù)時(shí)間)選擇合適的垃圾回收器。
2. 并行(Parallel) = 并發(fā)(Concurrent)
一個(gè)GC周期(Garbage Collection cycle)可以以STW(Stop-The-World)的形式出現(xiàn),這會(huì)發(fā)生一次GC停頓,也可以并發(fā)地執(zhí)行從而無需暫停應(yīng)用程序。更進(jìn)一步來 講,GC算法本身可以是串行的(單線程),也可以是并行的(多線程)。因此當(dāng)我們提到并發(fā)的GC時(shí),并不代表它是并行完成的,相反當(dāng)提到串行GC時(shí),也并 不意味著就一定會(huì)出現(xiàn)GC停頓。在GC的世界中,并發(fā)和并行是兩個(gè)完全不同的概念。并發(fā)針對(duì)的是GC周期,而并行針對(duì)GC算法自身。
結(jié)論:垃圾回收的過程實(shí)際上有兩步,啟動(dòng)GC周期和GC自身運(yùn)行,這是不同的兩件事。
3. G1能解決所有問題
經(jīng)過一系列修正和改 進(jìn),Java 7中引入了G1回收器,它是JVM垃圾回收器中最新的組件。G1最大的優(yōu)勢(shì)就是解決了CMS中常見的內(nèi)存碎片問題:GC周期會(huì)從老年代(Old Generation)中釋放內(nèi)存塊,結(jié)果內(nèi)存變得像瑞士奶酪那樣千瘡百孔,直到JVM對(duì)其無從下手了,才不得不停下來處理這些碎片。但是故事沒這么簡(jiǎn) 單,某些情況下其他回收器可能比G1有更好的表現(xiàn),這完全取決于你的需求。
結(jié)論:沒有一個(gè)奇跡般的回收器能解決所有GC問題,你應(yīng)該通過具體實(shí)驗(yàn)來選擇合適的回收器。
4. 平均事務(wù)時(shí)間是最需要被關(guān)注的指標(biāo)
如 果你僅僅監(jiān)控服務(wù)器的平均事務(wù)時(shí)間,那么很可能錯(cuò)過一些異常值。這些異常的情況可能對(duì)用戶來說是毀滅性的,而人們沒有意識(shí)到它的重要性。比如一個(gè)事務(wù)在正 常情況下耗時(shí)100ms,但受到GC停頓的影響,花了1分鐘才完成。除了用戶沒人會(huì)注意到這個(gè)問題,因?yàn)槟阒挥^察了平均事務(wù)時(shí)間。試想有1%或者更多的用 戶經(jīng)歷了這個(gè)場(chǎng)景,如果只關(guān)注平均值,它就太容易被忽略了。想了解更多和延遲相關(guān)的問題和怎樣正確處理,可以在這里閱讀Gil Tene的博客。
結(jié)論:留心那些異常值,你可以知道系統(tǒng)最后那1%的狀況。(可不是這個(gè)1%)
5. 降低新對(duì)象的分配率可以改善GC的運(yùn)行狀況
我們可以 粗略地把系統(tǒng)中的對(duì)象分為三種:長(zhǎng)命(long-lived)對(duì)象,對(duì)它們我們一般做不了什么;中等壽命(mid-lived)對(duì)象,最大的問題可能出現(xiàn) 在這;短命(short-lived)對(duì)象,它們的釋放和回收通常都很快,在下個(gè)GC周期來臨時(shí)就會(huì)消失。專注于中等壽命對(duì)象的分配率可以帶來有益的結(jié) 果,這對(duì)短命和長(zhǎng)命的對(duì)象卻不是那么有效。另外,控制中等壽命對(duì)象往往是一項(xiàng)困難的工作。
結(jié)論:給服務(wù)器帶來壓力的并不單純是對(duì)象的分配率,在運(yùn)行過程中這些對(duì)象的種類才是一切麻煩的根源。
6. 調(diào)優(yōu)可以解決所有事
如果你的程序需要保存大量被頻繁修改的狀態(tài),對(duì)JVM堆內(nèi)存進(jìn)行調(diào)優(yōu)就無法帶來很好的收益。較長(zhǎng)的GC停頓是不可避免的。一個(gè)解決辦 法是對(duì)架構(gòu)進(jìn)行改善,保證一個(gè)對(duì)響應(yīng)時(shí)間有決定性影響或者造成瓶頸的過程中,不包含大量狀態(tài)。大量狀態(tài)和響應(yīng)能力是難以良好共存的,因此將它們分開處理才 是上上之選。
結(jié)論:不是所有的問題都可以通過調(diào)整JVM參數(shù)解決,有時(shí)你只需要回顧自己的繪圖板。(譯注:重新審視程序的設(shè)計(jì))
7. GC日志會(huì)導(dǎo)致巨大的系統(tǒng)開銷
簡(jiǎn)單來說,這是錯(cuò)的,尤 其在默認(rèn)的日志配置下。日志數(shù)據(jù)是極為有價(jià)值的,Java 7中還引入了鉤子來控制它們的大小,保證硬盤空間不被用盡。如果不收集GC日志,那么你會(huì)失去這幾乎是唯一的,知曉JVM垃圾回收器在生產(chǎn)環(huán)境中工作狀態(tài) 的方法。一般可接受的GC開銷以5%作為上限,如果你能知道系統(tǒng)為GC停頓付出的代價(jià),也能對(duì)最小化這個(gè)代價(jià)采取行動(dòng),這種程度的開銷是不值一提的。
結(jié)論:在能力范圍內(nèi),盡可能多地獲取系統(tǒng)在生產(chǎn)環(huán)境中的運(yùn)行數(shù)據(jù),你會(huì)發(fā)現(xiàn)那是一個(gè)全新的世界。
總結(jié)
希望上面的結(jié)論能幫助你們更好地把握J(rèn)ava垃圾回收器的工作。在你們的程序中出現(xiàn)過類似問題嗎?你們周圍還有沒有其他對(duì)GC常見的誤解?請(qǐng)?jiān)谙旅娴脑u(píng)論區(qū)留言。