對于Java開發(fā)人員來說,,了解垃圾回收機(jī)制(GC)有哪些好處呢,?首先可以滿足作為一名軟件工程師的求知欲,其次,,深入了解GC如何工作可以幫你寫出更好的Java應(yīng)用,。 這僅僅代表我個(gè)人的意見,但我堅(jiān)信一個(gè)精通GC的人往往是一個(gè)好的Java開發(fā)者,。如果你對GC的處理過程感興趣,,說明你已經(jīng)具備較大規(guī)模應(yīng)用的開發(fā)經(jīng)驗(yàn)。如果你曾經(jīng)想過如何正確的選擇GC算法,,那意味著你已經(jīng)完全理解你所開發(fā)的應(yīng)用的特點(diǎn),。當(dāng)然,我們不能以偏概全,,這不能作為評價(jià)一個(gè)好的開發(fā)人員的共通標(biāo)準(zhǔn),。但是,我要說的是,,深入理解GC是成為一名偉大的程序員的必經(jīng)之路。 這是成為JavaGC專家系列文章的第一篇,,本篇主要針對GC機(jī)制進(jìn)行介紹,,在下一篇中,我們將重點(diǎn)探討分析GC狀態(tài)以及來自NHN的GC調(diào)優(yōu)的例子。 本文的目的是以一種簡單的方式向你介紹GC機(jī)制。我希望這些文章能夠幫到你,。實(shí)際上,,我的學(xué)生已經(jīng)在Twitter上發(fā)布了一些很好的關(guān)于Java內(nèi)核的文章,并且大受歡迎,。有興趣的話,你也可以關(guān)注他們。 回到正題,,咱們繼續(xù)談垃圾回收,在學(xué)習(xí)GC之前,,你首先應(yīng)該記住一個(gè)單詞:“stop-the-world”,。Stop-the-world會在任何一種GC算法中發(fā)生。Stop-the-world意味著 JVM 因?yàn)橐獔?zhí)行GC而停止了應(yīng)用程序的執(zhí)行,。當(dāng)Stop-the-world發(fā)生時(shí),,除了GC所需的線程以外,所有線程都處于等待狀態(tài),,直到GC任務(wù)完成,。GC優(yōu)化很多時(shí)候就是指減少Stop-the-world發(fā)生的時(shí)間。 按代的垃圾回收機(jī)制 在Java程序中不能顯式地分配和注銷內(nèi)存,。有些人把相關(guān)的對象設(shè)置為null或者調(diào)用System.gc()來試圖顯式地清理內(nèi)存,。設(shè)置為null至少沒什么壞處,但是調(diào)用System.gc()會顯著地影響系統(tǒng)性能,,必須徹底杜絕(還好,,我還沒有見到NHN的哪個(gè)開發(fā)者調(diào)用這個(gè)方法)。 在Java中,,開發(fā)人員無法直接在程序代碼中清理內(nèi)存,,而是由垃圾回收器自動尋找不必要的垃圾對象,并且清理掉他們,。垃圾回收器會在下面兩種假設(shè)(hypotheses)成立的情況下被創(chuàng)建(稱之為假設(shè)不如改為推測(suppositions)或者前提(preconditions)),。
這些假設(shè)我們稱之為弱年代假設(shè)( weak generational hypothesis)。為了強(qiáng)化這一假設(shè),,HotSpot虛擬機(jī)將其物理上劃分為兩個(gè)–新生代(young generation)和老年代(old generation),。 老年代(Old generation): 對象沒有變得不可達(dá),,并且從新生代中存活下來,,會被拷貝到這里。其所占用的空間要比新生代多,。也正由于其相對較大的空間,發(fā)生在老年代上的GC要比新生代少得多,。對象從老年代中消失的過程,,我們稱之為”major GC“(或者”full GC“) 請看下面這個(gè)圖表,。 圖1 : GC 空間 & 數(shù)據(jù)流 上圖中的持久代( permanent generation )也被稱為方法區(qū)(method area),。他用來保存類常量以及字符串常量,。因此,,這個(gè)區(qū)域不是用來永久的存儲那些從老年代存活下來的對象,。這個(gè)區(qū)域也可能發(fā)生GC。并且發(fā)生在這個(gè)區(qū)域上的GC事件也會被算為major GC,。 有些人可能會問: 圖 2: Card Table 結(jié)構(gòu) 新生代的構(gòu)成 為了更好地理解GC,,我們現(xiàn)在來學(xué)習(xí)新生代,,新生代是用來保存那些第一次被創(chuàng)建的對象,,他可以被分為三個(gè)空間
一共有三個(gè)空間,其中包含兩個(gè)幸存者空間,。每個(gè)空間的執(zhí)行順序如下:
如果你仔細(xì)觀察這些步驟就會發(fā)現(xiàn),,其中一個(gè)幸存者空間必須保持是空的。如果兩個(gè)幸存者空間都有數(shù)據(jù),,或者兩個(gè)空間都是空的,,那一定標(biāo)志著你的系統(tǒng)出現(xiàn)了某種錯誤。 圖 3: GC執(zhí)行前后對比 需要注意的是HotSpot虛擬機(jī)使用了兩種技術(shù)來加快內(nèi)存分配,。他們分別是是”bump-the-pointer“和“TLABs(Thread-Local Allocation Buffers)”,。 Bump-the-pointer技術(shù)跟蹤在伊甸園空間創(chuàng)建的最后一個(gè)對象。這個(gè)對象會被放在伊甸園空間的頂部,。如果之后再需要創(chuàng)建對象,,只需要檢查伊甸園空間是否有足夠的剩余空間。如果有足夠的空間,,對象就會被創(chuàng)建在伊甸園空間,,并且被放置在頂部。這樣以來,,每次創(chuàng)建新的對象時(shí),,只需要檢查最后被創(chuàng)建的對象。這將極大地加快內(nèi)存分配速度,。但是,,如果我們在多線程的情況下,事情將截然不同,。如果想要以線程安全的方式以多線程在伊甸園空間存儲對象,,不可避免的需要加鎖,而這將極大地的影響性能,。TLABs 是HotSpot虛擬機(jī)針對這一問題的解決方案,。該方案為每一個(gè)線程在伊甸園空間分配一塊獨(dú)享的空間,,這樣每個(gè)線程只訪問他們自己的TLAB空間,再與bump-the-pointer技術(shù)結(jié)合可以在不加鎖的情況下分配內(nèi)存,。 老年代GC處理機(jī)制 老年代空間的GC事件基本上是在空間已滿時(shí)發(fā)生,,執(zhí)行的過程根據(jù)GC類型不同而不同,因此,,了解不同的GC類型將有助于你理解本節(jié)的內(nèi)容,。
其中,Serial GC不應(yīng)該被用在服務(wù)器上,。這種GC類型在單核CPU的桌面電腦時(shí)代就存在了,。使用Serial GC會顯著的降低應(yīng)用的性能指標(biāo)。 1. Serial GC (-XX:+UseSerialGC) 新生代空間的GC方式我們在前面已經(jīng)介紹過了,,在老年代空間中的GC采取稱之為”mark-sweep-compact“的算法。
最后一步,,從頭開始,順序地填滿堆內(nèi)存空間,,并且將對內(nèi)存空間分成兩部分:一個(gè)保存著對象,,另一個(gè)空著(壓縮)。 2. Parallel GC (-XX:+UseParallelGC) 圖 4: Serial GC 與 Parallel GC的區(qū)別 從上圖中,,你可以輕易地看出serial GC和parallel GC的區(qū)別,,serial GC只使用一個(gè)線程執(zhí)行GC,而parallel GC使用多個(gè)線程,,因此parallel GC更高效,。這種GC在內(nèi)存充足以及多核的情況下會很有用,因此我們也稱之為”throughput GC“,。 3. Parallel Old GC(-XX:+UseParallelOldGC) Parallel Old GC在JDK5之后出現(xiàn),。與parallel GC相比,唯一的區(qū)別在于針對老年代的GC算法。Parallel Old GC分為三步:標(biāo)記-匯總-壓縮(mark – summary – compaction),。匯總(summary)步驟與清理(sweep)的不同之處在于,,其將依然幸存的對象分發(fā)到GC預(yù)先處理好的不同區(qū)域,算法相對清理來說略微復(fù)雜一點(diǎn),。 4. CMS GC (-XX:+UseConcMarkSweepGC) 圖 5: Serial GC & CMS GC 就像你從上圖看到的那樣, CMS GC比我之前解釋的各種算法都要復(fù)雜很多,。第一步初始化標(biāo)記(initial mark) 比較簡單。這一步驟只是查找那些距離類加載器最近的幸存對象,。因此,,停頓的時(shí)間非常短暫。在之后的并行標(biāo)記( concurrent mark )步驟,,所有被幸存對象引用的對象會被確認(rèn)是否已經(jīng)被追蹤和校驗(yàn),。這一步的不同之處在于,在標(biāo)記的過程中,,其他的線程依然在執(zhí)行,。在重新標(biāo)記(remark)步驟,,會再次檢查那些在并行標(biāo)記步驟中增加或者刪除的與幸存對象引用的對象,。最后,在并行交換( concurrent sweep )步驟,,轉(zhuǎn)交垃圾回收過程處理,。垃圾回收工作會在其他線程的執(zhí)行過程中展開。一旦采取了這種GC類型,,由GC導(dǎo)致的暫停時(shí)間會極其短暫,。CMS GC也被稱為低延遲GC。它經(jīng)常被用在那些對于響應(yīng)時(shí)間要求十分苛刻的應(yīng)用之上,。 當(dāng)然,,這種GC類型在擁有stop-the-world時(shí)間很短的優(yōu)點(diǎn)的同時(shí),也有如下缺點(diǎn):
在使用這個(gè)GC類型之前你需要慎重考慮,。如果因?yàn)閮?nèi)存碎片過多而導(dǎo)致壓縮任務(wù)不得不執(zhí)行,,那么stop-the-world的時(shí)間要比其他任何GC類型都長,你需要考慮壓縮任務(wù)的發(fā)生頻率以及執(zhí)行時(shí)間,。 5. G1 GC 最后,,我們來學(xué)習(xí)垃圾回收優(yōu)先(G1)GC類型。 圖 6: G1 GC的結(jié)構(gòu) 如果你想要理解G1,,首先你要忘記你所學(xué)過的新生代和老年代的概念,。正如你在上圖所看到的,每個(gè)對象被分配到不同的格子,,隨后GC執(zhí)行,。當(dāng)一個(gè)區(qū)域裝滿之后,對象被分配到另一個(gè)區(qū)域,并執(zhí)行GC,。這中間不再有從新生代移動到老年代的三個(gè)步驟,。這個(gè)類型是為了替代CMS GC而被創(chuàng)建的,因?yàn)镃MS GC在長時(shí)間持續(xù)運(yùn)作時(shí)會產(chǎn)生很多問題,。 G1最大的好處是性能,,他比我們在上面討論過的任何一種GC都要快。但是在JDK 6中,,他還只是一個(gè)早期試用版本,。在JDK7之后才由官方正式發(fā)布。就我個(gè)人看來,,NHN在將JDK 7正式投入商用之前需要很長的一段測試期(至少一年),。因此你可能需要再等一段時(shí)間。并且,,我也聽過幾次使用了JDK 6中的G1而導(dǎo)致Java虛擬機(jī)宕機(jī)的事件,。請耐心的等到它更穩(wěn)定吧。 下一次我將討論GC優(yōu)化相關(guān)的問題,,但是在此之前我要先明確一件事情,,假如應(yīng)用中創(chuàng)建的所有對象的大小和類型都是統(tǒng)一的,那么公司使用的WAS的GC參數(shù)可以是相同的,。但是WAS所創(chuàng)建對象的大小和生命周期根據(jù)服務(wù)以及硬件的不同而不同,。換句話說,不能因?yàn)槟硞€(gè)應(yīng)用使用的GC參數(shù)“A”,,就說明同樣的參數(shù)也能給其他服務(wù)帶來最佳的效果,。而是要因地制宜,有的放矢,。我們需要找到適合每個(gè)WAS線程的參數(shù),,并且持續(xù)的監(jiān)控和優(yōu)化每個(gè)設(shè)備上的WAS實(shí)例。這并不是我的一家之談,,而是負(fù)責(zé)Oracle Java虛擬機(jī)研發(fā)的工程師在 JavaOne 2010上已經(jīng)討論過的,。 本文中我們簡略的介紹了Java的GC機(jī)制,請繼續(xù)關(guān)于我們的后續(xù)文章,,我們將會討論如何監(jiān)控Java GC狀態(tài)以及優(yōu)化GC,。 另外,我特別推薦一本2011年12月發(fā)布的《Java性能》(Amazon,也可以通過safari在線閱讀),,還有在Oracle官網(wǎng)發(fā)布的白皮書《Java HotSpotTM虛擬機(jī)內(nèi)存管理》(這本書與Java性能優(yōu)化不是同一本) 作者Sangmin Lee, NHN公司,,性能工程師實(shí)驗(yàn)室高級工程師。 |
|