1. 程式人生 > >垃圾收集器與內存分配策略

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

使用 對象比較 內存空間 強引用 公司 enc 虛引用 我們 內存溢出

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

垃圾回收機制(Garbage Collection,GC),GC的歷史要比java悠久。1960年誕生於MIT的Lisp是第一個真正使用內存動態分配和垃圾收集技術的語言。當時人們考慮GC需要解決三件事:

  • 哪些內存需要回收
  • 什麽時候回收
  • 如何回收

1.1 對象怎麽判斷是否需要回收

判斷對象是否存活,如果存活則不需要回收,如果死亡則需要回收。引用計數算法和可達性分析算法是兩個判斷對象是否死亡的算法。

引用計數算法:就是當沒有任何引用指向這個對象時,及判定此對象已死亡。但是他有缺點,當兩個對象的屬性分別指向兩個對象,然後把兩個對象的引用置為null,並且這兩個對象已經沒有其他引用指向這兩個對象。此時這兩個對象是已經死亡,但是他們又有其屬性指向對方,所以引用計數並非為0。下面舉個列子:

/** *testGC()方法執行後,objA和objB會不會被GC呢?

*

*/

public class ReferenceCountingGC{

public Object instance=null;

private static final int_1MB=1024*1024;

/**

*這個成員屬性的唯一意義就是占點內存,以便能在GC日誌中看清楚是否被

*回收過

*/

private byte[]bigSize=new byte[2*_1MB];

public static void testGC(){

ReferenceCountingGC objA=new ReferenceCountingGC();

ReferenceCountingGC objB=new ReferenceCountingGC();

objA.instance=objB;

objB.instance=objA;

objA=null;

objB=null;

//假設在這行發生GC,objA和objB是否能被回收?

System.gc();

} }

可達性分析算法:現在一些主流的語言判斷對象是否已死都是通過可達性分析算法實現的。這個算法的實現思路:通過一系列的”GC Roots”為起點,從這個節點往下收索,節點直接的路徑稱為引用鏈,假如有些對象沒有與”GC Roots”起始節點聯系,則為對象不可達,既判斷這個對象已死。在java語言中有哪些對象可以作為”GC Roots”呢?

  1. 虛擬機棧(棧幀中的本地變量表)中引用的對象.
  2. 方法區靜態屬性引用的對象
  3. 方法區中常量引用的對象
  4. 本地方法棧Native中引用的對象

技術分享

因為Object4、Object5、Object6是不可達的,所以就認為這三個對象以及死亡.Object1、Object2、Object3是可達的,所以這三個對象還還存活。

1.2 引用

自jdk 1.2 以後,java將引用分為:強引用(Strong Reference),軟引用(Soft Reference),弱引用(Weak Reference),虛引用(Phantom Reference),這4種的引用強度依次逐漸減弱。

強引用: Object obj=new Object(); new出來的對象都是強引用。只要強引用存在,垃圾收集器就不會回收被引用的對象。

軟引用:用於還有些用但並非必需的對象。系統在發生內存溢出之前,會把這種類型的對象進行二次回收。如果這次回收還沒有足夠的內存,則系統會拋出內存溢出的異常。在JDK1.2之後,提供了SoftReference類來實現軟引用。

弱引用:用來描述非必需的對象,它的強度比軟引用的強度弱一些。當下一次垃圾收集器發生的時候,無論內存是否只夠都會回收掉只被弱引用的對象。在JDK1.2之後,提供了WeakReference類來實現弱引用。

虛引用也稱為幽靈引用或者幻影引用,它是最弱的一種引用關系。一個對象是否有虛引 用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象實例。為一 個對象設置虛引用關聯的唯一目的就是能在這個對象被收集器回收時收到一個系統通知。在 JDK 1.2之後,提供了PhantomReference類來實現虛引用。

強:String abc=new String(“abc”);

軟:SoftReference<String> sr=new SoftReference<String>(“abc”);

弱: WeakReference<String> abcWeakRef = new WeakReference<String>(abc);

1.3 垃圾收集算法

標記-清除算法:算法分為”標記”和”清除”兩個階段。首先標記出需要回收的對象,標記分為兩次輪詢,第一次會執行finalize(),當這個方法執行後該對象仍然處於回收的隊列則,第二次輪詢該對象就會被回收。在這個方法中對象也可以自救。標記說完了就說一下清除,當確認這個對象已死,這系統就會把這個對象回收了稱為清除。這個算法有兩個問題: 1標記和清除的效率都很低2.空間問題,對象被清除後,空間就會變的不連續,從而大大的損耗了系統內存。

技術分享

1.4 復制算法

為了解決效率問題,復制算法把空間劃分為等同的兩部分,一部分作為預留,一部分作為存儲。復制算法的思路是把存活的對象直接復制到另一個預留區域,然後把這個存儲區域直接清空。存儲區域變成了預留區域,預留區域變成了存儲區域。從未節省了空間。

技術分享

現在的商業虛擬機都采用這種收集算法來回收新生代,IBM公司的專門研究表明,新生 代中的對象98%是“朝生夕死”的,所以並不需要按照1:1的比例來劃分內存空間,而是將內存 分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor [1]。 當回收時,將Eden和Survivor中還存活著的對象一次性地復制到另外一塊Survivor空間上,最 後清理掉Eden和剛才用過的Survivor空間。HotSpot虛擬機默認Eden和Survivor的大小比例是 8:1,也就是每次新生代中可用內存空間為整個新生代容量的90%(80%+10%),只有10% 的內存會被“浪費”。當然,98%的對象可回收只是一般場景下的數據,我們沒有辦法保證每 次回收都只有不多於10%的對象存活,當Survivor空間不夠用時,需要依賴其他內存(這裏 指老年代)進行分配擔保(Handle Promotion)。

1.5 標記-整頓算法

我更喜歡稱他為:標記-清除-整頓算法。正如他的名字一樣,他是比標記,清除算法多了一個整頓的操作,復制算法適合與新生代,存活的對象比較少,這樣效率自然高。但卻不適合老年代,老年代每次回收的對象少存活的多,這樣使用復制算法效率就會很低,這個時候就適合標記整頓算法。

技術分享

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