Java中的記憶體回收原理

來源:互聯網
上載者:User
記憶體回收簡介

使用者程式(mutator)會修改還堆區中的對象集合,從儲存管理器處擷取空間,建立對象,還可一引入和消除對已有對象的引用。

當mutator不能“達到”某些對象的時候,這些對象就成了垃圾。

目的:找到不可達的對象,並將這些對象交給跟蹤空閑空間的儲存管理器,收回他們所佔的資源。

一些基本概念

型別安全:任何資料分量的類型都是可確定的。

可以在編譯時間刻確定資料的類型稱為靜態型別安全,運行時刻確定稱為動態類型。

類型不安拳的語言不適合使用自動記憶體回收。

在java中,除了整型和引用這樣的基本類型,所有對象都被分配在堆區而不是棧區。這種設計使得程式員不需要關注變數的生命週期,但代價是產生更多的垃圾。

可達性

對任何指標解引用就可以被程式直接存取的資料則為可達的。

局部性原理

        如果一個程式方位的儲存位置很可能將在一個很短的時間段再次被訪問,則稱這個程式具有時間局部性(Temporal locality)。如果被訪問過的儲存位置的臨近位置很可能在一個很短的時間段內被訪問,則該程式具有空間局部性。

        通常認為程式把90%的時間來執行10%的代碼。

幾種記憶體回收行程的原理

標記-清除收集器

這種收集器首先遍曆對象圖並標記可到達的對象,然後掃描堆棧以尋找未標記對象並釋放它們的記憶體。這種收集器一般使用單線程工作並停止其他動作。並且,由於它只是清除了那些未標記的對象,而並沒有對標記對象進行壓縮,導致會產生大量記憶體片段,從而浪費記憶體。

標記-壓縮收集器

有時也叫標記-清除-壓縮收集器,與標記-清除收集器有相同的標記階段。在第二階段,則把標記對象複製到堆棧的新域中以便壓縮堆棧。這種收集器也停止其他動作。

複製收集器

這種收集器將堆棧分為兩個域,常稱為半空間。每次僅使用一半的空間,JVM產生的新對象則放在另一半空間中。GC運行時,它把可到達對象複製到另一半空間,從而壓縮了堆棧。這種方法適用於短生存期的對象,持續複製長生存期的對象則導致效率降低。並且對於指定大小堆來說,需要兩倍大小的記憶體,因為任何時候都只使用其中的一半。

增量收集器

增量收集器把堆棧分為多個域,每次僅從一個域收集垃圾,也可理解為把堆棧分成一小塊一小塊,每次僅對某一個塊進行垃圾收集。這會造成較小的應用程式停機時間,使得使用者一般不能覺察到垃圾收集器正在工作。

部分回收原理

通常80%~90%的新指派至在幾百萬條指令之內或者再分配了。

分代收集器(Generational garbage coolection)

 它是基於拷貝回收器和部分回收原理。

充分利用大多數對象“英年早逝”的特性的有效方法。

將堆區分成一系列小的地區,用0,1,2......n對它們進行編號,序號越小的地區存放的對象越年輕,對象首先在0地區被建立,填滿後垃圾被回收,可達對象被移到1區,每一輪記憶體回收都是針對序號小於等於i的地區進行的,i為當前被填滿地區的最高編號。

只要對i進行回收,所有序號小雨i的地區也將要進行記憶體回收,應為較年輕的世代往往包含了較多的垃圾,也就是更頻繁的被回收。

最老的世代儲存了最成熟的對象,對這些對象的回收是最昂貴的,相當於一次完整的回收。可引起較長時間的停頓。解決方案是使用列車演算法。

Garbage Collectors in the J2SE 5.0 HotSpot JVM

記憶體回收行程的職責有1)分配記憶體;2)確保引用的記憶體在記憶體中;3)回收記憶體中不可達的對象。

制定記憶體回收演算法的時候需要做出一些權衡和選擇:1)串列還是並行;2)並發還是stop-the-world;3)記憶體緊緻還是不緊緻還是採用拷貝演算法。

J2SE 5.0 HotSpot JVM中的四種GC採用的是世代收集器的原理,如下:

    hotspot(j2se 5.0,按白皮書上的說法也適用於6.0)使用的是所謂的Generational Collection機制,也就是把對象分為young和old(還有一種是permanent),young對象經過幾次回收後(存活較長時間後),就會成為old對象.之所以採用這種機制,是基於以下觀察:大部分建立對象的引用不會持續太久,也就說是會die young;少部分的引用會持續下來.
    因此,young generation 進行Collection會更多,因此使用的演算法對時間效率的要求高.而old generation 儲存的資料較多,使用的演算法對空間的要求效率相對而言要求就較高了.把對象分為不同的generation,便於採用不同的演算法進行操作.
對應的,可以把HotSpotJVM 的記憶體分為:young generation ,oldgeneration ,permanent generation.在這裡預設首先把對像都放到地區的左邊.

J2SEHotSpot JVM 的 4中記憶體回收行程都是屬於generational collection.
一般說來,建立對象時一般都是在young裡面分配記憶體,old裡面儲存的是young中經過幾次回收依然存活的對象以及一些一開始就在old中分配的大對象.而permanent generation裡面儲存的是類資訊和中繼資料.
younggeneration分為一個Eden地區和兩個survivor 地區.對象在Eden地區產生,經過一次GC後存活下來的對象進入survivor中,並在此經過更嚴格的考驗,然後進入old .同一時間只有一個survivor地區儲存對象,另一個為空白.

當young地區滿了後,就會執行young地區的GC演算法.當old和permanent地區滿了後,會先執行young的GC.再執行old和permanent的GC,如果old地區對象過多無法執行young的GC,那麼在young地區執行old的GC演算法(因為記憶體空間耗費較少),但是CMS回收器的old演算法不行,下面會說明原因.

對於多線程的應用.JVM 使用一個Thread-LocalAllocation Buffers ,為每個線程分配一個地區來指派至,以排除線程競爭.如果此地區滿了的話就使用鎖.

HotSpot的四種GC 回收器:
序列化回收serial collector:
特點:回收時會暫停應用.
young地區:將Eden和某個Survivor地區中的存活的對象複製到另一個Survivor地區(設為TO)(大對象直接放到old地區).如果TO地區滿了,則直接複製到old地區.
old地區:使用mark-sweep-compact GC演算法,也就是先標記存活對象,然後清除廢棄對象,然後把存活對象都移到一塊地區,空出一片較大的空閑空間.
適用範圍:大部分用戶端的應用都可以使用這種回收演算法,這也是HotSpot預設的回收演算法.在現在的機器(06年)上一個64MB的地區的一次完全回收所需的時間不到半秒鐘.

並行回收parallel collector:
特點:可以利用多核的CPU.
young地區:同樣還是要暫停應用,基本機制和序列化差不多,不過是使用多線程.可以加快效率.
old地區:同序列化.
多核電腦上面可以使用.

並行壓縮回收parallel compacting collector:
與並行回收相比,主要是在old地區有個新的演算法,同時,按白皮書的說法,這種回收最終會替代並行回收.
young地區:同並行回收.
old地區:首先,把old分為幾個連續的地區.然後,在每個地區並行的進行檢查,標記出alive的對象(先標記出可以直接引用的對象,然後是所有的).然後開始對這些地區進行檢查,得出密集程度(左邊的地區肯定比右邊的密集),從某個密集程度不很高的地區開始,並行的對右邊地區進行壓縮.
適應範圍:對於多核,且對pause time有要求的環境下,使用並行壓縮回收比並行回收要好.但是對於高共用率的伺服器(也就說一台伺服器運行多個應用),由於old地區的collection較慢,又是多線程,所以一個應用的GC會對其他應用造成影響.對應的解決方案:可以配置減少並行時的線程數目.

並行標記清除回收Concurrent Mark Sweep collector:
young地區:同並行回收.
old地區:分為幾個步驟.
Initialmark:在需要執行GC時,先暫停應用,然後把所有直接引用到的對象進行標記.
Concurrentmark:然後繼續應用,並同時對已標記對象進行檢查,得到所有存活的對象.
remark:再次暫停應用,對Concurrent mark持續期間應用程式修改了的對象進行檢查(新增的,廢棄的),並標記存活對象.這個階段期間較長,因此會使用多線程.在階段結束後,所有的存活對象都被標記了,未標記的對象就是垃圾對象了.
sweep:停止暫停應用程式,然後把所有垃圾對象的空間釋放.


與其他演算法的不同點:
第一:不執行壓縮.不過會通過計算將來可能的記憶體需求而合并/分割某些記憶體塊.
第二:不是old地區要滿了才執行GC,而是在空間小於一定程度時開始.
第三:由於沒執行壓縮,因此會產生片段.

另外,CMS還可以使用增量運行方式,就是在Concurrentmark階段只執行一部分工作,然後把資源還給應用程式.回收器的工作會分為幾個部分並安排在兩次young地區的回收空閑階段完成.這種模式一般用在對暫停時間有要求,同時處理器數目不多的情況下(單核或雙核).
總體說來,與並行回收相比,CMS降低了old GC的暫停時間(有時候效果很顯著),輕微的加長了young GC的時間(因為對象從young地區轉到old地區時間會加長:沒執行壓縮,因此要先找到合適的地區),降低了整個系統的一些執行效率,以及很大的加強了對於記憶體空間的需求.

一些Java編程的建議

根據GC的工作原理,我們可以通過一些技巧和方式,讓GC運行更加有效率,更加符合應用程式的要求。一些關於程式設計的幾點建議:

1.最基本的建議就是儘早釋放無用對象的引用。大多數程式員在使用臨時變數的時候,都是讓引用變數在退出活動域(scope)後,自動化佈建為 null.我們在使用這種方式時候,必須特別注意一些複雜的對象圖,例如數組,隊列,樹,圖等,這些對象之間有相互參考關聯性較為複雜。對於這類對象,GC 回收它們一般效率較低。如果程式允許,儘早將不用的引用對象賦為null.這樣可以加速GC的工作。

2.盡量少用finalize函數。finalize函數是Java提供給程式員一個釋放對象或資源的機會。但是,它會加大GC的工作量,因此盡量少採用finalize方式回收資源。

3.如果需要使用經常使用的圖片,可以使用soft應用類型。它可以儘可能將圖片儲存在記憶體中,供程式調用,而不引起OutOfMemory.

4.注意集合資料類型,包括數組,樹,圖,鏈表等資料結構,這些資料結構對GC來說,回收更為複雜。另外,注意一些全域的變數,以及一些靜態變數。這些變數往往容易引起懸掛對象(dangling reference),造成記憶體浪費。

5.當程式有一定的等待時間,程式員可以手動執行System.gc(),通知GC運行,但是Java語言規範並不保證GC一定會執行。使用增量式GC可以縮短Java程式的暫停時間。

參考

j2se 5.0 hotspot 的四種記憶體回收行程 - http://blog.csdn.net/dmy_110/article/details/8000007

探秘Java記憶體回收機制 - http://developer.51cto.com/art/201009/227691.htm

Memory Management in the JavaHotSpotTM Virtual Machine - 下載

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.