垃圾回收

一、java內存區域

5個區域:方法區,虛擬機棧,本地方法棧,堆,程序計數器。

程序計數器:線程私有

可以看做是當前字節碼的行號指示器。在虛擬機的概念模型裏,字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令。(分支,循環,跳轉,異常處理,線程恢復等基礎功能都需要依賴這個計數器完成)

java虛擬機棧:線程私有。

生命週期與線程相同,虛擬機棧描述的是java方法執行的內存模型:每個方法在執行時都會創建一個棧幀,用於存儲變量表,操作數棧,動態鏈接,方法出口等信息。

每一個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機中入棧到出棧的過程。

局部變量表:

存放了編譯器可知的各種基本數據類型(boolean,char,int,float,double,byte,short,long),對象引用(reference類型)和returnAddress類型(指向了一條字節碼指令的地址)。局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法需要在幀中分配多大的局部變量空間是完全確定的,在方法運行期間不會改變局部變量的大小。

本地方法棧:

與虛擬機棧的區別:虛擬機棧爲虛擬機執行java方法(字節碼)服務,本地方法棧則爲虛擬機使用到的native方法服務。

Sun HotSpot虛擬機把本地方法棧和虛擬機棧合二爲一。

java堆:線程共享

java heap是java虛擬機所管理的內存中最大的一塊,用於存放對象實例,虛擬機啓動時創建。java堆是垃圾收集器管理的主要區域,也叫GC堆。採用分代收集算法

方法區:線程共享

用於存儲已被虛擬機加載的類信息,常量,靜態變量,即時編輯器編譯後的代碼等數據。(HotSpot虛擬機將GC分代收集擴展至方法區,用永久代來實現方法區)

運行時常量池:

方法區的一部分,用於存放編譯期生成的各種字面量和符號引用,這部分內容在類加載後進入方法區的運行時常量池中存放。

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

生存還是死亡:如何判斷對象還活着?

1.引用計數算法:

給對象添加一個引用計數器,每當有一個地方引用它時,計數器值加1,當引用失效時,計數器值減1,任何時候計數器值爲0就是不可能再被使用的。

但是,很難解決對象之間互相循環引用的問題。虛擬機不是通過這個方法來判斷對象是否存活的。

package edu.xatu.gc;

public class GC {

    public Object instance = null;
    public static void testGC(){
        GC obja = new GC();
        GC objb = new GC();
        obja.instance = objb;
        objb.instance = obja;
        
        obja = null;
        objb = null;
        
//        
        System.gc();
    }
}

上面的代碼,obja,objb已經爲空,不可能再被引用,但是因爲互相引用着對方,引用計數器不爲0,按理說不能回收,但是實際虛擬機已經回收了。

2.可達性分析算法:

基本思想:通過一系列的稱爲“GC Roots”的對象作爲起始點,從這個節點開始向下搜索,搜索所走過的路徑稱爲引用鏈。當一個對象到GC Roots沒有任何引用鏈相連時,則此對象不可用。

可作爲GC Roots的對象有:

(1)虛擬機棧(棧幀中的本地變量表)中引用的對象

(2)方法區中類靜態屬性引用的對象

(3)方法區中常量引用的對象

(4)本地方法棧中JNI即native方法引用的對象

垃圾收集算法:

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

首先標記出所有需要回收的對象,標記完成後統一回收所有被標記的對象。

不足:效率低,標記,清除兩個過程效率都低。標記清除後會出現大量不連續的內存碎片。

(2)複製算法:(新生代)

將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中一塊。當這一塊內存用完了,就把還存活着的對象複製到另一塊上面,然後把這一塊內存空間一次清理掉。

雖然效率高,但是內存縮小了一半,代價高。

對象存活率較高時進行較多的複製操作,所以用於回收新生代(朝生夕死),將內存分爲一塊Eden和兩塊Survivor空間,每次使用Eden和一塊Survivor,回收時,將Eden和這塊Survivor還存活着的對象複製到另一塊survivor空間上,清除掉Eden和這塊Survivor。HotSpot虛擬機默認Eden和Survivor的大小比例是8:1

(3)標記-整理算法:(老年代)

標記的過程與標記-清除算法一樣,但是後續不是直接對可回收對象急性清理,而是讓所有存活的對象都像一端移動,然後直接清除掉端便捷以外的內存。

分代收集算法:

根據對象存活週期不同,將java堆分爲新生代和老年代,根據各個年代的特點選擇最適用的的收集算法。

3.內存分配與回收策略:
(1)對象優先在Eden分配

大多數情況下,對象在新生代Eden區中分配,當Eden沒有足夠空間進行分配時,虛擬機會發起一次Minor GC

Minor GC:新生代GC,比較頻繁,回收速度也比較快

Major GC/Full GC:發生在老年代的GC,速度較慢。

(2)大對象直接進入老年代

大對象:需要大量連續內存空間的JAVA對象,比如很長的字符串以及數組byte[]數組。最怕遇到一羣朝生夕滅的短命大對象。

(3)長期存活的對象將進入老年代

虛擬機給每個對象定義了一個對象年齡計數器,如果對象在Eden出生並且經過一次Minor GC仍然存在,並且能夠被Survivor容納的話,將被移動到Survivor空間中,年齡設爲1.在Survivor中每熬過一次GC,年齡就增加1歲,當增加到一定程度時(默認15),會被晉升到老年代中。

(4)動態對象年齡判定

並不是永遠要求對象的年齡必須達到maxTenuringThreshold才能晉升到老年代。如果Survivor空間中相同年齡所有對象大小的綜合大於Survivor空間的一半,年齡大於或者等於該年齡的對象就可以直接進入老年代,無須等到要求的年齡。

空間分配擔保:

在發生Minor GC 之前,虛擬機會先檢查老年代最大可用的連續空間是否大於新生代所有對象總空間,如果這個條件成立,那麼Minor GC可以確定是安全的。如果不成立,則虛擬機會查看HandlePromotionFailure設置值是否允許擔保失敗。如果允許,那麼會繼續檢查老年代最大可用的連續空間是否大於歷次晉升到老年代對象的平均大小,如果大於,將嘗試着進行一次Minor GC,儘管這次是有風險的。如果小於,或者HandlePromotionFailure設置不允許冒險,那這次也要改爲進行一次Full GC

JDK 6 Update 24之後,HandlePromotionFailure參數不會再影響到虛擬機的空間分配擔保策略。規則變爲:只要老年代的連續空間大於新生代對象總大小或者歷次晉升的平均大小就會進Minor GC,否則將進行Full GC

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