JAVA 垃圾收集器與內存分配策略

引言

垃圾收集技術並非Java語言獨創的,1960年誕生於MIT的Lisp是第一門真正使用內存動態分配和垃圾收集技術的語言。垃圾收集技術須要考慮的三個問題是:java

哪些內存須要回收?
何時回收?
如何回收?算法

http://segmentfault.com/a/119... 中講到java內存運行時區域的分佈,其中程序計數器,虛擬機棧,本地方法區都是隨着線程而生,隨線程而滅,因此這幾個區域就不須要過多考慮回收問題。可是堆和方法區就不同了,只有在程序運行期間咱們才知道會建立哪些對象,這部份內存的分配和回收都是動態的。垃圾收集器所關注的就是這部份內存。segmentfault

一 對象死亡判據

垃圾收集器在對一個對象回收以前,首先要判斷對象在程序中是否還有使用的可能性,充要條件就是沒有被程序可訪問的引用再指向這個對象實例。最簡單的辦法就是給對象實例添加中添加一個引用計數器,每當有一個引用指向它時,計數器就加一,當引用失效時,計數器就減一,若是計數器值爲0則說明沒有引用指向它,能夠進行回收。可是這個方法中計數器爲0並非一個必要條件,例如,生成兩個對象實例,每一個對象實例的屬性都指向對方,那麼這個兩個對象實例分別最少有一個引用。數組

java採用的是可達性分析算法,即找一部分對象做爲"GC Roots"節點,從這些節點開始向下搜索,當某個對象到"GC Roots"節點沒有可達路徑時,說明此對象是不可用的。在java中做爲"GC Roots"的節點包括:安全

  1. 虛擬機棧中引用的對象,多線程

  1. 方法區靜態屬性引用的對象,併發

  2. 方法區常量引用的對象,spa

  3. 本地方法區中本地調用所引用的對象。線程

161641_uW2m_1983603.png

引用擴充

若是reference類型的數據中存儲的數值是另外一塊內存的起始地址,那麼這塊內存就表明着一個引用。一個對象在這種狀態下,只能有被引用和沒有被引用兩種狀態。java對引用概念進行了擴充,將引用分爲強引用(new),軟引用(softReference),弱引用(WeakReference),虛引用(PhantomReference)。若是強引用存在,則垃圾收集器不會回收該對象。若是系統即將發生內存溢出異常,那麼垃圾回收集器則會回收軟引用對象。弱引用對象只能存活到下一次垃圾收集以前。虛引用對象不會對其生存時間構成任何影響。code

對象的自我救贖

在垃圾收集器發現某一個對象到"GC Roots"路徑不可達時,先會判斷該對象是否覆蓋finalize()方法,或是否執行過finalize()方法。若是覆蓋了且沒有執行過該方法,則會將該對象放到低優先級的Finalizer線程隊列中去執行finalize()方法,若是在finalize()方法中該對象又被引用,則會有一次逃脫被回收的命運。

方法區的回收

方法區中主要回收廢棄的常量和無用的類。對於常量,若是沒有引用指向常量,則該常量會被回收。對於類的回收則麻煩許多,首先要判斷該類是無用的類無用的類要知足三個條件:

  1. 全部類的實例被回收。

  2. 加載該類的ClassLoader已經被回收。

  3. Class沒有被引用,不會經過反射訪問該類的方法。

二 垃圾回收算法

標記-清除算法(Mark-Sweep)

該算法分爲兩個階段:首先標記處要回收的對象,標記完成後統一回收全部被標記的對象。
存在的問題:

  1. 標記和清除效率都不高

  2. 標記清除後會產生大量內存碎片,分配大對象時可能觸發另外一次垃圾收集。

162639_lRPR_1983603.png

複製算法(Copying)

該算法將內存分爲兩個等大小的區域,每次只使用一個區域。當一個區域快用完了,就將這個區域中存活的對象複製到另外一個區域。

優勢是避免了內存碎片的產生,缺點是浪費內存空間。

162929_kGQX_1983603.png

有公司研究代表,新生代的對象98%都是朝生暮死,因此虛擬機把新生代內存劃分爲一個較大的Eden空間和兩個較小的Survivor空間。每次只是用Eden空間和一個Survior空間,當進行復制清理時,將Survivor空間和Eden空間中存活的對象複製到另外一塊Survivor空間。當Survivor空間不夠用時,就會依賴老年代進行分配擔保。

標記-整理算法(Mark-Compact)

針對老年代對象存活率高的狀況,複製算法明顯不合適,因而採用標記整理算法,標記和標記清除算法相同,二後邊的整理則是讓全部存活的對象都向一端移動,而後清理掉邊界外的內存。

163309_pPkR_1983603.png

分代收集

當前虛擬機都採用分代收集,分代的依據是對象的存活週期。通常新生代存活率低,採用複製算法。老年代存活率高採用標記整理標記清除

通常來說,新生代空間會小不少,具體比例通常要看應用場景。而默認的大小通常是老年代的1/4到1/3。

三 垃圾收集器

114752_Cpuf_1983603.jpg
因爲虛擬機採用了分代收集,因此針對不一樣代收集器也不一樣。上圖是HotSpot虛擬機的垃圾收集器,連線表示能夠協同工做。

Serial收集器,複製算法,它是一個單線程的收集器,而且在進行收集時會暫停其餘線程,它默認是client模式下的新生代收集器。

ParNew收集器是Serial收集器的多線程版,它是第一款併發收集器。

Parallel Scavenge收集器能夠精確控制吞吐量(用戶代碼運行時間/(用戶代碼時間+垃圾收集時間))

SerialOld收集器是serial收集器的老年版,採用標記整理算法,一樣是單線程收集器。

ParallelOld是ParallelScavenge收集器的老年版,使用多線程和標記整理算法。

CMS收集器是以最短回收停頓時間爲目標的收集器,採用標記清除算法,在重視響應速度的系統中得以應用。可是缺點是對CPU資源敏感,沒法處理浮動垃圾,易產生內存碎片。

G1收集器是最新推出的收集器,可應用在JDK1.7u4及以上版本。它將內存分爲多個Region,新生代和老年代分別包含多個Region。G1跟蹤各個Region,判斷垃圾價值大小,優先回收價值最大的Region。

安全點

安全點的概念是指當進行GC時,應當讓工做線程中止,這時會更容易對對象是否存活進行判斷。而中止線程應當在安全的時刻,因此會有安全點的概念。

暫停線程有兩種方式,第一種是強制暫停,若是某些線程沒有到達安全點則再讓他運行到安全點,這叫作搶先式中斷。第二種是在編譯過程當中在安全點加入一個條件判斷判斷0x160100內存頁是否可讀,若是不可讀則會暫停,這叫作主動式中斷

四 內存分配與回收策略

163144_0sxV_1983603.png
對象的分配,就是在堆上分配,對象主要分配在新生代的Eden區域中,若是啓動了本地線程分配緩衝,則按線程優先在TLAB中分配。少數狀況也有可能直接分配到老年代。

對象在Eden區域分配時,當Eden區域沒有足夠空間,虛擬機會發起一次新生代垃圾收集。

若是對象須要大量連續內存空間,例如String類型和數組。大對象對於虛擬機內存分配來講是一個壞消息,朝生暮死的大對象是要命的壞消息。常常出現大對象會致使屢次出發垃圾收集。對於這類對象,能夠設置參數將大對象直接存入老年代。

每個對象都有一個年齡計數器,當對象在Eden區域出生,每通過一次GC,而且存入Survivor,計數器加一。當年齡增長到必定程度(默認15),則會被存入老年代。同時,若是Survivor空間中相同年齡對象佔空間超過50%,則也會直接進入老年代。

總結

垃圾收集算法:複製算法標記-清除算法標記-清理算法

垃圾收集器特色:新生代用複製,老年代用標記清理,CMS用標記清除。

Eden空間大小和Survivor空間大小默認比率爲8:1,即新生代10%的空間用來存放複製後的對象。

更多文章:http://blog.gavinzh.com

相關文章
相關標籤/搜索