摘要:本文是成為專家系列的第一篇。然而,在多線程環(huán)境下,將會有別樣的狀況。在中正是通過解決了多線程問題。在最后的并發(fā)清理階段,垃圾回收過程被真正執(zhí)行。在垃圾回收執(zhí)行過程中,其他線程依然在執(zhí)行。
原文鏈接:http://www.cubrid.org/blog/de...
了解Java的垃圾回收(GC)原理能給我們帶來什么好處?對于軟件工程師來說,滿足技術(shù)好奇心可算是一個,但重要的是理解GC能幫忙我們更好的編寫Java應用程序。
上面是我個人的主觀的看法,但我相信熟練掌握GC是成為優(yōu)秀Java程序員的必備技能。如果你對GC執(zhí)行過程感興趣,也許你只是有一定的開發(fā)應用的經(jīng)驗;如果你仔細考慮過如何選擇合適的GC算法,說明你對你所開發(fā)的程序有了全面的了解。當然這對一個優(yōu)秀的程序員來說未必是一個通用的標準,但很少人會反對我關(guān)于"理解GC是作為優(yōu)秀Java程序員的必備技能"的看法。
本文是成為Java GC專家系列的第一篇。我將先對GC做一下基本的概述,在下一篇文章中,我將講述如何分析GC狀態(tài)以及通過[NHN]()的案例介紹GC調(diào)優(yōu)相關(guān)的內(nèi)容。
本文的目的是以通俗的方式為你介紹GC概念。我希望本文會對你有所幫助。事實上,我的同事們已經(jīng)發(fā)表了一些在Twitter上非常受關(guān)注的優(yōu)秀文章,你同樣也可以拿來參考。
回到垃圾回收上,在開始學習GC之前你應該知道一個詞:stop-the-world。不管選擇哪種GC算法,stop-the-world都是不可避免的。Stop-the-world意味著從應用中停下來并進入到GC執(zhí)行過程中去。一旦Stop-the-world發(fā)生,除了GC所需的線程外,其他線程都將停止工作,中斷了的線程直到GC任務結(jié)束才繼續(xù)它們的任務。GC調(diào)優(yōu)通常就是為了改善stop-the-world的時間。
基于的分代理論的垃圾回收在Java程序里不需要顯式的分配和釋放內(nèi)存。有些人通過給對象賦值為null或調(diào)用System.gc()以期望顯式的釋放內(nèi)存空間。給對象設(shè)置null雖沒什么用,但問題不會太大;如果調(diào)用了System.gc()卻可能會為系統(tǒng)性能帶來嚴重的波動,即便調(diào)用System.gc()系統(tǒng)也未必立即響應去執(zhí)行垃圾回收。(所幸的是,在NHN未曾看到有工程師這么做。)
在使用Java時,程序員不需要在程序代碼中顯式的釋放內(nèi)存空間,垃圾回收器會幫你找到不再需要的(垃圾)對象并把他們移出。垃圾回收器的創(chuàng)建基于以下兩個假設(shè)(也許稱之為推論或前提更合適):
大多數(shù)對象的很快就會變得不可達
只有極少數(shù)情況會出現(xiàn)舊對象持有新對象的引用
這兩條假設(shè)被稱為"弱分代假設(shè)"。為了證明此假設(shè),在HotSpot VM中物理內(nèi)存空間被劃分為兩部分:新生代(young generate)和老年代(old generation)。
新生代:大部分的新創(chuàng)建對象分配在新生代。因為大部分對象很快就會變得不可達,所以它們被分配在新生代,然后消失不再。當對象從新生代移除時,我們稱之為"minor GC"。
老年代:存活在新生代中但未變?yōu)椴豢蛇_的對象會被復制到老年代。一般來說老年代的內(nèi)存空間比新生代大,所以在老年代GC發(fā)生的頻率較新生代低一些。當對象從老年代被移除時,我們稱之為"major GC"(或者full GC)。
看一下下圖的示意:
圖1:GC區(qū)域和數(shù)據(jù)流向
圖中的permanent generation稱為方法區(qū),其中存儲著類和接口的元信息以及interned的字符串信息。所以這一區(qū)域并不是為老年代中存活下來的對象所定義的持久區(qū)。方法區(qū)中也會發(fā)生GC,這里的GC同樣也被稱為major GC。
有些人可能認為:
如果老年代的對象需要持有新生代對象的引用怎么辦?
為了處理這種場景,在老年代中設(shè)計了"索引表(card table)",是一個512字節(jié)的數(shù)據(jù)塊。不管何時老年代需要持有新生代對象的引用時,都會記錄到此表中。當新生代中需要執(zhí)行GC時,通過搜索此表決定新生代的對象是否為GC的目標對象,從而降低遍歷所有老年代對象進行檢查的代價。該索引表使用寫柵欄(write barrier)進行管理。wite barrier是一個允許高性能執(zhí)行minor GC的設(shè)備。盡管它會引入一定的開銷,卻能帶來總體GC時間的大幅降低。
圖2:索引表結(jié)構(gòu)
為了深入理解GC,我們先從新生代開始學起。所有的對象在初始創(chuàng)建時都會被分配在新生代中。新生代又可分為三個部分:
一個Eden區(qū)
兩個Survivor區(qū)
在三個區(qū)域中有兩個是Survivor區(qū)。對象在三個區(qū)域中的存活過程如下:
大多數(shù)新生對象都被分配在Eden區(qū)。
第一次GC過后Eden中還存活的對象被移到其中一個Survivor區(qū)。
再次GC過程中,Eden中還存活的對象會被移到之前已移入對象的Survivor區(qū)。
一旦該Survivor區(qū)域無空間可用時,還存活的對象會從當前Survivor區(qū)移到另一個空的Survivor區(qū)。而當前Survivor區(qū)就會再次置為空狀態(tài)。
經(jīng)過數(shù)次在兩個Survivor區(qū)域移動后還存活的對象最后會被移動到老年代。
如上所述,兩個Survivor區(qū)域在任何時候必定有一個保持空白。如果同時有數(shù)據(jù)存在于兩個Survivor區(qū)或者兩個區(qū)域的的使用量都是0,則意味著你的系統(tǒng)可能出現(xiàn)了運行錯誤。
下圖向你展示了經(jīng)過minor GC把數(shù)據(jù)遷移到老年代的過程:
圖3: GC前后
在HotSpot VM中,使用了兩項技術(shù)來實現(xiàn)更快的內(nèi)存分配:"指針碰撞(bump-the-pointer)"和"TLABs(Thread-Local Allocation Buffers)"。
Bump-the-pointer技術(shù)會跟蹤在Eden上新創(chuàng)建的對象。由于新對象被分配在Eden空間的最上面,所以后續(xù)如果有新對象創(chuàng)建,只需要判斷新創(chuàng)建對象的大小是否滿足剩余的Eden空間。如果新對象滿足要求,則其會被分配到Eden空間,同樣位于Eden的最上面。所以當有新對象創(chuàng)建時,只需要判斷此新對象的大小即可,因此具有更快的內(nèi)存分配速度。然而,在多線程環(huán)境下,將會有別樣的狀況。為了滿足多個線程在Eden空間上創(chuàng)建對象時的線程安全,不可避免的會引入鎖,因此隨著鎖競爭的開銷,創(chuàng)建對象的性能也大打折扣。在HotSpot中正是通過TLABs解決了多線程問題。TLABs允許每個線程在Eden上有自己的小片空間,線程只能訪問其自己的TLAB區(qū)域,因此bump-the-pointer能通過TLAB在不加鎖的情況下完成快速的內(nèi)存分配。
本小節(jié)快速瀏覽了新生代上的GC知識。上面講的兩項技術(shù)無需刻意記憶,只需要明白對象開始是創(chuàng)建在Eden區(qū),然后經(jīng)過在Survivor區(qū)域上的數(shù)次轉(zhuǎn)移而存活下來的長壽對象最后會被移到老年代。
老年代垃圾回收當老年代數(shù)據(jù)滿時,便會執(zhí)行老年代垃圾回收。根據(jù)GC算法的不同其執(zhí)行過程也會有所區(qū)別,所以當你了解了每種GC的特點后再來理解老年代的垃圾回收就會容易很多。
在JDK 7中,內(nèi)置了5種GC類型:
Serial GC
Parallel GC
Parallel Old GC(Parallel Compacting GC)
Concurrent Mark & Sweep GC (or "CMS")
Garbage First (G1) GC
其中Serial GC務必不要在生產(chǎn)環(huán)境的服務器上使用,這種GC是為單核CPU上的桌面應用設(shè)計的。使用Serial GC會明顯的損耗應用的性能。
下面分別介紹每種GC的特性。
Serial GC(-XX:+UseSerialGC)在前面介紹的年輕代垃圾回收中使用了這種類型的GC。在老年代,則使用了一種稱之為"mark-sweep-compact"的算法。
首先該算法需要在老年代中標記出存活著的對象
然后從前到后檢查堆空間中存活的對象,并保持位置不變(把不再存活的對象清理出堆空間,稱為空間清理)
最后,把存活的對象移到堆空間的前面部分以保持已使用的堆空間的連續(xù)性,從而把堆空間分為兩部分:有對象的和無對象的(稱為空間壓縮)
Serial GC適用于CPU核數(shù)較少且使用的內(nèi)存空間較小的場景。
Parallel GC(-XX:+UseParallelGC)
圖4:Serial GC與Parallel GC的區(qū)別
圖中可以容易的看出serial GC與parallel GC的區(qū)別。Serial GC使用單一線程執(zhí)行GC,而parallel GC則使用多個線程并發(fā)執(zhí)行,因此parallel GC 較serial GC具有更快的速度。Parallel GC適用于多核CPU且使用了較大內(nèi)存空間的場景。Parallel GC又被稱為"高吞吐GC(throughput GC)"
Parallel Old GC(-XX:+UseParallelOldGC)Parallel Old GC在JDK 5中被引入,與Parallel GC相比唯一的區(qū)別在于Parallel的GC算法是為老年代設(shè)計的。它的執(zhí)行過程分為三步:標記(mark)--總結(jié)(summary)--壓縮(compaction)。其中summary步驟會會分別為存活的對象在已執(zhí)行過GC的空間上標出位置,因此與mark-sweep-compact算法中的sweep步驟有所區(qū)別,并需要一些復雜步驟才能完成。
CMS GC(-XX:+UseConcMarkSweepGC)
圖5:Serial GC與CMS GC
從圖上可看出并發(fā)標記-清理(Concurrent Mark-Sweep) GC比以后上其他GC都要復雜。開始時的初始標記(initial mark)比較簡單,只有靠近類加載器的存活對象會被標記,因此停頓時間(stop-the-world)比較短暫。在并發(fā)標記(concurrent mark)階段,由剛被確認和標記過的存活對象所關(guān)聯(lián)的對象將被會跟蹤和檢測存活狀態(tài)。此步驟的不同之處在于有多個線程并行處理此過程。在重標記(remark)階段,由并發(fā)標記所關(guān)聯(lián)的新增或中止的對象瘵被會檢測。在最后的并發(fā)清理(concurrent sweep)階段,垃圾回收過程被真正執(zhí)行。在垃圾回收執(zhí)行過程中,其他線程依然在執(zhí)行。得益于CMS GC的執(zhí)行方式,在GC期間系統(tǒng)中斷時間非常短暫。CMS GC也被稱為低延遲GC,適用于所有應用對響應時間要求比較嚴格的場景。
CMS GC雖然具有中斷時間斷的優(yōu)勢,其缺點也比較明顯:
與其他GC相比,CMS GC要求更多的內(nèi)存空間和CPU資源
CMS GC默認不提供內(nèi)存壓縮
使用CMS GC之前需要對系統(tǒng)做全面的分析。另外為了避免過多的內(nèi)存碎片而需要執(zhí)行壓縮任務時,CMS GC會比任何其他GC帶來更多的stop-the-world時間,所以你需要分析和判斷壓縮任務執(zhí)行的頻率及其耗時情況。
G1 GC最后我們學習有關(guān)G1垃圾回收的介紹。
圖6:G1 GC的布局
如果你想清晰的理解GC,請先忘記上面介紹的有關(guān)新生代和老年代的知識。如上圖所示,每個對象在創(chuàng)建時會分析到一個格子中,后續(xù)的GC也是在格子中完成的。每當一個區(qū)域分配滿對象后,新創(chuàng)建的對象就會分配到另外一個區(qū)域,并開始執(zhí)行GC。在這種GC中不會出現(xiàn)其他GC中的對象在新生代和老生代三區(qū)域中移動的現(xiàn)象。G1是為了取代在長期使用中暴露出大量問題且飽受抱怨的CMS GC。
G1最大的改進在于其性能表現(xiàn),它比以上任何一種GC都更快速。它在JDK6中以早期版本的形式釋放出來以用于測試,它真正的發(fā)布是在JDK7中。我個人認為在NHN真正在生產(chǎn)環(huán)境使用JDK7至少還需要1年的測試時間,所以還需要等待一段時間。并且我聽說在JDK6中使用G1偶爾會出現(xiàn)JVM崩潰現(xiàn)象。所以穩(wěn)定版尚需時日。
接下來的文章中會講解GC調(diào)優(yōu),但我想先提一個問題。如果應用中所有對象的類型和大小都是一樣的,WAS上使用的GC可以設(shè)置相同的GC選項。如果在WAS上創(chuàng)建的對象的大小和生命周期各不相同的對象,配置的GC選項也各不相同。換名話說,不能因為一個服務使用了GC選項"A",其他的不同服務使用相同的選項"A"也能獲取最好的表現(xiàn)。所以為了找到WAS線程的最佳值,每個WAS實例需要通過持續(xù)的調(diào)優(yōu)和監(jiān)控以便找到最優(yōu)的配置和GC優(yōu)項。這不只是來自我的個人經(jīng)驗,而是來自于JavaOne 2010上工程師們對于Oracle JVM討論后的一致看法。
本節(jié)我們只簡單介紹Java中的GC基礎(chǔ)。下一章節(jié),我將會討論關(guān)于如何監(jiān)控GC狀態(tài)以及如何做性能調(diào)優(yōu)。
本文參考了2011年12月出版的《Java 性能》和Oracle網(wǎng)站上提供的白皮書《Java HotspotTM 虛擬機內(nèi)存管理》。
作者:Sangmin Lee, 性能實驗室高級工程師,NHN公司
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/65345.html
摘要:本文將介紹的參數(shù)的重要性以及在發(fā)生時對系統(tǒng)整體性能的顯著影響。我們來看下的選項在發(fā)生時會對系統(tǒng)帶來哪些影響。所以這些請求將會放到堆積隊列,隊列的長度是的中設(shè)置的。從而導致進程的數(shù)量超過,并觸發(fā)了操作系統(tǒng)進行內(nèi)存交換的閥值。 原文鏈接:http://www.cubrid.org/blog/dev-platform/maxclients-in-apache-and-its-effect-o...
摘要:原文鏈接這是專家系列文章的第二篇。運行在本地虛擬機上的應用的又稱為,通常與相同。性能數(shù)據(jù)需要持續(xù)觀察,因此在運行時需要定時輸出的監(jiān)控信息。新生代容量的統(tǒng)計信息。是提供的一個式的圖表監(jiān)控工具。 原文鏈接:http://www.cubrid.org/blog/dev-platform/how-to-monitor-java-garbage-collection/ 這是GC專家系列文章的第二...
摘要:在本文中我將會介紹應用性能優(yōu)化的一般原則。性能優(yōu)化的流程圖摘取自和合著的性能,描述了應用性能優(yōu)化的處理流程。例如,對每臺服務器,你面臨著為單個分配堆內(nèi)存和運行個并為每個分配堆內(nèi)存的選擇。不過位能使用堆內(nèi)存最大理論值只有。 原文鏈接:http://www.cubrid.org/blog/dev-platform/the-principles-of-java-application-per...
摘要:原文鏈接本篇是專家系列的第三篇。但是,請記住調(diào)優(yōu)是不得已時的選擇。縮短耗時的單次執(zhí)行與相比,耗時有較明顯的增加。創(chuàng)建文件過程中,進程會中斷,因此不要在正常運行時系統(tǒng)上做此操作。因此校驗結(jié)果并根據(jù)具體的服務需要,決定是否要進行調(diào)優(yōu)。 原文鏈接:http://www.cubrid.org/blog/dev-platform/how-to-tune-java-garbage-collecti...
摘要:產(chǎn)品熱門內(nèi)容優(yōu)化系列文章第一篇從和開始及系列文章牧曦之晨譯專家系列理解垃圾回收牧曦之晨譯專家系列垃圾回收的監(jiān)控的坐標空間及位置巧用的百分比值實現(xiàn)高度自適應多用于占位,避免閃爍和沒有一點關(guān)系顏海鏡不可錯過的迷你庫簡單的異步文件加載器工具名一實 SF 產(chǎn)品 news Noodles 《SegmentFault 熱門內(nèi)容優(yōu)化》 系列文章 neu 《Gradle for Android 第一...
閱讀 2099·2021-11-15 18:09
閱讀 976·2021-09-06 15:13
閱讀 2688·2021-08-23 09:43
閱讀 2068·2019-08-30 15:54
閱讀 2261·2019-08-30 13:56
閱讀 2532·2019-08-26 11:31
閱讀 3125·2019-08-26 10:56
閱讀 789·2019-08-26 10:28