java垃圾回收機制研究

1.什麼是java垃圾回收

       垃圾回收GC(Garbage Collection)簡單的一句話介紹:回收無任何引用的對象佔據的內存。html

        這個java虛擬機實現的,全部咱們java程序員,new了一個對象後,就不用管何時釋放這個對象的內存,後面java虛擬機會自動幫咱們釋放。java

        (咱們知道java中基本的數據類型是在棧裏面,因此編譯器是知道該數據的存活時間,能夠自動釋放,可是java的對象都是在堆裏存儲,是經過new出來了,沒有像c++中的局部對象,因此編譯器也就不知道存儲的數據在堆裏存活活多長時間,因此要程序來釋放,c++中經過析構函數釋放,而java基本經過虛擬機垃圾回收釋放,不用本身寫)c++

        其實垃圾回收並非java的專利,不少語言都有垃圾回收機制,1960年 基於MIT的Lisp首先提出了垃圾回收的概念,用於處理C語言等不停的析構操做,而這時Java尚未出世呢!因此實際上GC並非Java的專利,GC的歷史遠遠大於Java的歷史!               程序員

   其實垃圾回收機制主要作如下兩件事算法

       1.回收無任何引用的對象佔據的內存空間網絡

       2.整理內存碎片,將其融合。多線程

       因爲建立對象和垃圾回收器釋放對象,因此會出現內存碎片,碎片是分配給對象塊之間的內存空洞,碎片整理是將對象讓其一個挨着一個,推到堆的一邊,這樣整理出來的內存就能夠分給新建立的對象了。併發

2.爲何要研究垃圾回收機制

      理解GC工做機制能夠幫助你寫出更好的Java應用程序,咱們能夠:函數

一、    排查內存溢出

二、    排查內存泄漏

三、    性能調優,排查併發瓶頸

       不少人誤覺得java有了垃圾回收機制以後,就不會有內存泄漏,這是錯誤的理解,由於java裏面的對象並不是老是被垃圾回收,及對象可能不被垃圾回收,有些特殊狀況,即經過建立對象之外的方式爲對象分配了存儲空間。這種時候,垃圾回收就不會回收,咱們可使用finalize()的方式來釋放內存,這種特殊狀況舉例:分配內存時可能用到了相似C語言中的作法,而非java中的一般作法,這種狀況主要發生在「本地方法」的狀況下,本地方法是一種在java中調用非java代碼的方式。性能

3.垃圾回收機制的原理和算法

3.1  引用記數法

        是一種很簡單但速度很慢的垃圾回收技術,每一個對象都含有一個引用計數器,當有引用鏈接至該對象的時候,引用記數加1,當引用離開做用域或被置爲null時,引用記數減一,雖然管理引用記數的開銷不大,可是這項開銷在整個程序生命週期中將持續發送,垃圾回收器會在含有所有對象的列表上遍歷,當發現某個對象的引用記數爲0時,就釋放其佔用的空間。

當咱們的代碼出現下面的情形時,該算法將沒法適應

a)         ObjA.obj = ObjB

b)         ObjB.obj - ObjA

                 這樣的代碼會產生以下引用情形 objA指向objB,而objB又指向objA,這樣當其餘全部的引用都消失了以後,objA和objB還有一個相互的引用,也就是說兩個對象的引用計數器各爲1,而實際上這兩個對象都已經沒有額外的引用,已是垃圾了。

               


     缺點:若是對象之間有循環引用時,可能會出現「對象應該被回收,可是記數卻不爲零」的狀況。

這種垃圾回收機制彷佛從未被應用到任何一種java虛擬機中。

3.2 根搜索算法

         在如今的垃圾回收器基本採用的是這種方法來尋找活的對象,這種思路的原理是,任何 「活」的對象,必定能最終追溯到其存活在堆棧或靜態存儲區之中的引用,這個引用鏈條可能會穿過數個對象層次。

         因此根搜索算法以下:

         從堆棧和靜態存儲區開始,遍歷全部的引用,就能找到全部「活」的對象。對於發現的每一個引用,必須追蹤他所引用的對象,若是這個對象還包含引用,就必須追蹤這些引用的所引用的對象,依次遞歸下去,直到「根源於堆棧和靜態存儲區中引用」所造成的網絡被所有訪問爲止,你所訪問過的對象必須是「活」的。

       這樣就解決了「交互自引用」的對象組的問題---由於這種對象根本就不會被發現,所以也就被自動回收了。

       算法以下圖

其實根節點能夠是以下:

  •     虛擬機棧中引用的對象(本地變量表)
  •     方法區中靜態屬性引用的對象
  •     方法區中常量引用的對象
  •     本地方法棧中引用的對象(Native對象)

       如今的垃圾收集器基本都是使用這個方法來收集垃圾,而收集後的垃圾是經過什麼算法來回收的呢?

一、    標記-清除算法

二、    複製算法

三、    標記-整理算法

四、    分代收集算法

咱們來逐一過一下

一、    標記-清除算法

        



 

 

標記-清除算法採用從根集合進行掃描,對存活的對象對象標記,標記完畢後,再掃描整個空間中未被標記的對象,進行回收,如上圖所示。

標記-清除算法不須要進行對象的移動,而且僅對不存活的對象進行處理,在存活對象比較多的狀況下極爲高效,但因爲標記-清除算法直接回收不存活的對象,所以會形成內存碎片!

二、    複製算法

 

 



 

   

 複製算法採用從根集合掃描,並將存活對象複製到一塊新的,沒有使用過的空間中,這種算法當控件存活的對象比較少時,極爲高效,可是帶來的成本是須要一塊內存交換空間用於進行對象的移動。也就是咱們前面提到的s0 s1等空間。

 

三、    標記-整理算法

 

 



 

  

 標記-整理算法採用標記-清除算法同樣的方式進行對象的標記,但在清除時不一樣,在回收不存活的對象佔用的空間後,會將全部的存活對象往左端空閒空間移動,並更新對應的指針。標記-整理算法是在標記-清除算法的基礎上,又進行了對象的移動,所以成本更高,可是卻解決了內存碎片的問題。

四、分代收集算法

       當前的商業虛擬機中基本都是使用「分代收集」算法,這種算法並無什麼新的思想,只是根據對象存活週期的不一樣將內存劃分爲幾塊,通常是把java堆分爲了新生代和老年代,在新生代中,每次垃圾收集時都發現有大批對象死去,只有少許存活,而老年代中對象存活率高,這樣就能夠根據各個年代的特定採用最適當的收集算法。

       目前大部分垃圾收集器對於新生代都採起stop-copy算法,由於新生代中每次都要回收大量的對象,因此複製的對象少,可是實際中並非按照1:1的比例來劃分新生代的空間的,通常來講是將新生代劃分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden空間和其中的一塊Survivor空間,當進行回收時,將Eden和Survivor中還存活的對象複製到另外一塊Survivor空間中,而後清理掉Eden和剛纔使用過的Survivor空間。

       而因爲老年代的特色是每次回收都只回收少許對象,通常使用的是Mark-Compact算法。

     對象的內存分配,往大方向上講就是在堆上分配,對象主要分配在新生代的Eden Space和From Space,少數狀況下會直接分配在老年代。若是新生代的Eden Space和From Space的空間不足,則會發起一次GC,若是進行了GC以後,Eden Space和From Space可以容納該對象就放在Eden Space和From Space。在GC的過程當中,會將Eden Space和From  Space中的存活對象移動到To Space,而後將Eden Space和From Space進行清理。若是在清理的過程當中,To Space沒法足夠來存儲某個對象,就會將該對象移動到老年代中。在進行了GC以後,使用的即是Eden space和To Space了,下次GC時會將存活對象複製到From Space,如此反覆循環。當對象在Survivor區躲過一次GC的話,其對象年齡便會加1,默認狀況下,若是對象年齡達到15歲,就會移動到老年代中。

什麼狀況下觸發垃圾回收

    因爲對象進行了分代處理,所以垃圾回收區域、時間也不同。GC有兩種類型:Scavenge GCFull 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:

· 年老代(Tenured)被寫滿

· 持久代(Perm)被寫滿 

· System.gc()被顯示調用 

·上一次GC以後Heap的各域分配策略動態變化

4.java中典型的垃圾收集器

垃圾收集算法是 內存回收的理論基礎,而垃圾收集器就是內存回收的具體實現。下面介紹一下HotSpot(JDK 7)虛擬機提供的幾種垃圾收集器,用戶能夠根據本身的需求組合出各個年代使用的收集器。

  1.Serial/Serial Old

  Serial/Serial Old收集器是最基本最古老的收集器,它是一個單線程收集器,而且在它進行垃圾收集時,必須暫停全部用戶線程。Serial收集器是針對新生代的收集器,採用的是Copying算法,Serial Old收集器是針對老年代的收集器,採用的是Mark-Compact算法。它的優勢是實現簡單高效,可是缺點是會給用戶帶來停頓。

  2.ParNew

  ParNew收集器是Serial收集器的多線程版本,使用多個線程進行垃圾收集。

  3.Parallel Scavenge

  Parallel Scavenge收集器是一個新生代的多線程收集器(並行收集器),它在回收期間不須要暫停其餘用戶線程,其採用的是Copying算法,該收集器與前兩個收集器有所不一樣,它主要是爲了達到一個可控的吞吐量。

  4.Parallel Old

  Parallel Old是Parallel Scavenge收集器的老年代版本(並行收集器),使用多線程和Mark-Compact算法。

  5.CMS

  CMS(Current Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器,它是一種併發收集器,採用的是Mark-Sweep算法。

   

CMS 處理過程有七個步驟: 
        a. 初始標記(CMS-initial-mark) ,會致使swt; 
        b. 併發標記(CMS-concurrent-mark),與用戶線程同時運行; 
        c. 預清理(CMS-concurrent-preclean),與用戶線程同時運行; 
        d. 可被終止的預清理(CMS-concurrent-abortable-preclean) 與用戶線程同時運行; 
        e. 從新標記(CMS-remark) ,會致使swt; 
        f. 併發清除(CMS-concurrent-sweep),與用戶線程同時運行; 
        g. 併發重置狀態等待下次CMS的觸發(CMS-concurrent-reset),與用戶線程同時運行; 

  6.G1

  G1收集器是當今收集器技術發展最前沿的成果,它是一款面向服務端應用的收集器,它能充分利用多CPU、多核環境。所以它是一款並行與併發收集器,而且它能創建可預測的停頓時間模型。

參考資料

    http://jbutton.iteye.com/blog/1569746

    http://www.cnblogs.com/dolphin0520/p/3783345.html

    http://pengjiaheng.iteye.com/blog/524024

    https://www.jianshu.com/p/2a1b2f17d3e4

    https://blog.csdn.net/zqz_zqz/article/details/70568819

相關文章
相關標籤/搜索