JVM垃圾回收機制

任何一種垃圾回收算法一般要做2件基本的事情:
(1)發現無用信息對象;
(2)回收被無用對象佔用的內存空間,使該空間可被程序再次使用。
現在的垃圾回收基本都採用分代收集算法
介紹幾種算法:
1.引用計數法(Reference Counting Collector)
很難解決循環引用的問題,主流JVM都沒有選用RCC
2.根搜算法(可達性分析算法)
在主流的商用程序語言中(Java和C#),都是使用根搜索算法(GC Roots Tracing)判斷對象是否存活的
在Java語言裏,可作爲GC Roots對象的包括如下幾種:
a.虛擬機棧(棧楨中的本地變量表)中的引用的對象
b.方法區中的類靜態屬性引用的對象
c.方法區中的常量引用的對象
d.本地方法棧中JNI的引用的對象
3.標記 -清除算法
  最基礎的收集算法是“標記-清除”(Mark-Sweep)算法,如它的名字一樣,算法分爲“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成後統一回收掉所有被標記的對象,它的標記過程就是根搜索算法。之所以說它是最基礎的收集算法,是因爲後續的收集算法都是基於這種思路並對其缺點進行改進而得到的。它的主要缺點有兩個:一個是效率問題,標記和清除過程的效率都不高;另外一個是空間問題,標記清除之後會產生大量不連續的內存碎片,空間碎片太多可能會導致,當程序在以後的運行過程中需要分配較大對象時無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作
4.複製算法
  爲了解決效率問題,一種稱爲“複製”(Copying)的收集算法出現了,它將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另外一塊上面,然後再把已使用過的內存空間一次清理掉。這樣使得每次都是對其中的一塊進行內存回收,內存分配時也就不用考慮內存碎片等複雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。只是這種算法的代價是將內存縮小爲原來的一半,未免太高了一點。複製算法的執行過程如圖3-3所示。
現在的商業虛擬機都採用這種收集算法來回收新生代,IBM的專門研究表明,新生代中的對象98%是朝生夕死的,所以並不需要按照1∶1的比例來劃分內存空間,而是將內存分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中的一塊Survivor。當回收時,將Eden和Survivor中還存活着的對象一次性地拷貝到另外一塊Survivor空間上,最後清理掉Eden和剛纔用過的Survivor的空間。HotSpot虛擬機默認Eden和Survivor的大小比例是8∶1,也就是每次新生代中可用內存空間爲整個新生代容量的90%(80%+10%),只有10%的內存是會被“浪費”的。當然,98%的對象可回收只是一般場景下的數據,我們沒有辦法保證每次回收都只有不多於10%的對象存活,當Survivor空間不夠用時,需要依賴其他內存(這裏指老年代)進行分配擔保(Handle Promotion)。

5.標記-整理算法
  複製收集算法在對象存活率較高時就要執行較多的複製操作,效率將會變低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。
  根據老年代的特點,有人提出了另外一種“標記-整理”(Mark-Compact)算法,標記過程仍然與“標記-清除”算法一樣,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的內存。

6.分代收集算法
  當前商業虛擬機的垃圾收集都採用“分代收集”(Generational Collection)算法,這種算法並沒有什麼新的思想,只是根據對象的存活週期的不同將內存劃分爲幾塊。一般是把Java堆分爲新生代和老年代,這樣就可以根據各個年代的特點採用最適當的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用複製算法,只需要付出少量存活對象的複製成本就可以完成收集。而老年代中因爲對象存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記-清理”或“標記-整理”算法來進行回收。

GC的執行機制
由於對象進行了分代處理,因此垃圾回收區域、時間也不一樣。GC有兩種類型:Scavenge GC和Full GC。
Scavenge GC
一般情況下,當新對象生成,並且在Eden申請空間失敗時,就會觸發Scavenge GC,對Eden區域進行GC,清除非存活對象,並且把尚且存活的對象移動到Survivor區。然後整理Survivor的兩個區。這種方式的GC是對年輕代的Eden區進行,不會影響到年老代。因爲大部分對象都是從Eden區開始的,同時Eden區不會分配的很大,所以Eden區的GC會頻繁進行。因而,一般在這裏需要使用速度快、效率高的算法,使Eden去能儘快空閒出來。
Full GC
對整個堆進行整理,包括Young、Tenured和Perm。Full GC因爲需要對整個堆進行回收,所以比Scavenge GC要慢,因此應該儘可能減少Full GC的次數。在對JVM調優的過程中,很大一部分工作就是對於FullGC的調節。有如下原因可能導致Full GC:
1.年老代(Tenured)被寫滿
2.持久代(Perm)被寫滿
3.System.gc()被顯示調用
4.上一次GC之後Heap的各域分配策略動態變化

JVM內存分爲:年輕代,年老代,持久代(持久代又稱爲方法區)
年輕代:HotSpot JVM把年輕代分爲了三部分:1個Eden區和2個Survivor區(分別叫from和to)。默認比例爲8:1
GC算法採用的是“標記-整理”算法:
在GC開始的時候,對象只會存在於Eden區和名爲“From”的Survivor區,Survivor區“To”是空的。緊接着進行GC,Eden區中所有存活的對象都會被複制到“To”,而在“From”區中,仍存活的對象會根據他們的年齡值來決定去向。年齡達到一定值(年齡閾值,可以通過-XX:MaxTenuringThreshold來設置)的對象會被移動到年老代中,沒有達到閾值的對象會被複制到“To”區域。經過這次GC後,Eden區和From區已經被清空。這個時候,“From”和“To”會交換他們的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎樣,都會保證名爲To的Survivor區域是空的。Minor GC會一直重複這樣的過程,直到“To”區被填滿,“To”區被填滿之後,會將所有對象移動到年老代中。

finalize()方法:
finalize相當於析構函數,是垃圾回收器回收一個對象的時候第一個要調用的方法,一般的純Java編寫的Class不需要重新覆蓋這個方法
之所以要使用finalize(),是存在着垃圾回收器不能處理的特殊情況
特殊的區域例如:
1)由於在分配內存的時候可能採用了類似 C語言的做法,而非JAVA的通常new做法。這種情況主要發生在native method中,比如native method調用了C/C++方法malloc()函數系列來分配存儲空間,但是除非調用free()函數,否則這些內存空間將不會得到釋放,那麼這個時候就可能造成內存泄漏。但是由於free()方法是在C/C++中的函數,所以finalize()中可以用本地方法來調用它。以釋放這些“特殊”的內存空間。
2)打開的文件資源,這些資源不屬於垃圾回收器的回收範圍。

注:
1. 新生代(New Generation)
大多數情況下Java程序中新建的對象都從新生代分配內存
2. 老年代(Old Generation或Tenuring Generation)
用於存放新生代中經過多次垃圾回收仍然存活的對象,例如緩存對象,新建的對象也有可能在老年代上直接分配內存。比如過大對象

四種引用:
1.強引用就是指在程序代碼之中普遍存在的,類似“Object obj = new Object()”這類引用,只要強引用存在,垃圾收集器永遠不會回收掉被引用的對象。
2.軟引用用來描述一些還有用,但並非必須的對象。對於軟引用關聯着的對象,在系統將要發生內存溢出之前,將會把這些對象列進回收範圍之 中,並進行第二次回收。如果這次回收還是沒有足夠的內存,纔會拋出內存溢出錯誤。在JDK1.2之後,提供了SoftReference來實現軟引用。
3.弱引用也是用來描述非必須對象的,但是它的強度比軟引用更弱一點,被弱引用關聯的對象只能生存到下一次垃圾回收之前。當垃圾收集器工作時,無論當前內存是否足夠,都會回收掉被弱引用關聯的對象。在JDK1.2之後,提供了WeakReference來實現弱引用。
引用隊列(Reference Queue)
一旦弱引用對象開始返回null,該弱引用指向的對象就被標記成了垃圾。而這個弱引用對象(非其指向的對象)就沒有什麼用了。通常這時候需要進行一些清理工作。比如WeakHashMap會在這時候移除沒用的條目來避免保存無限制增長的沒有意義的弱引用。
引用隊列可以很容易地實現跟蹤不需要的引用。當你在構造WeakReference時傳入一個ReferenceQueue對象,當該引用指向的對象被標記爲垃圾的時候,這個引用對象會自動地加入到引用隊列裏面。接下來,你就可以在固定的週期,處理傳入的引用隊列,比如做一些清理工作來處理這些沒有用的引用對象。
4.虛引用它是最弱的一種引用關係。一個對象是否有虛引用存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象的實例。爲 一個對象設置虛引用關聯的唯一目的是希望能在這個對象被收集器回收時收到一個系統通知。在JDK1.2之後,提供了PhantomReference

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章