JVM 之 內存分配與回收策略

不詩意的女程序媛不是好廚師~ 轉載請註明出處,From李詩雨---[blog.csdn.net/cjm24848365…]java

本篇思惟導圖

1.GC(Garbage Collection垃圾回收)

1.1 誰須要GC?

:重點!面試

方法區/元空間:(只須要知道這裏 也有垃圾回收 便可)算法

棧: 不須要 。線程私有的,隨線程消亡而消亡,不須要過多考慮垃圾回收問題。數組

1.2 GC 觸發的條件:內存不夠了

新生代不夠了 → Minor GC緩存

老年代不夠了 → Full GCide

補充:堆的進一步劃分

堆的進一步劃分

新生代(PSYoungGen)this

​ 又分爲:spa

​ •Eden空間.net

​ •From Survivor空間線程

​ •To Survivor空間

▶老年代(ParOldGen)

2. GC如何判斷對象是否存活?

2.1 引用計數算法

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

優勢:

快、方便、實現簡單。

▶缺點:

對象互相引用時很難判斷對象是否該回收

2.2 可達性分析 (面試重點)▲▲▲

在這裏插入圖片描述

這個算法的基本思路就是經過一系列的稱爲「GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可用的。

做爲GC Roots的對象包括下面幾種:

1.虛擬機棧(棧幀中的本地變量表)中的對象。(方法中的參數,方法體中的局部變量)

2.方法區中 類靜態屬性的對象。 (static)

3.方法區中 常量的對象。 (final static)

4.本地方法棧中 JNI(即通常說的Native方法)的對象。

來個栗子:

public class GCRoots {

Object o =new Object(); //o不是GCRoots,方法運行完之後,o可回收。
static Object GCRoot1 =new Object(); //GC Roots---方法區中 類靜態屬性的對象
final  static Object GCRoot2 =new Object();//GCRoots---方法區中 常量的對象

public static void main(String[] args) {
    //可達
    Object object1 = GCRoot1; //注意:「 = 」 不是賦值,在對象中是引用,傳遞的是右邊對象的地址
    Object object2 = object1;
    Object object3 = object1;
    Object object4 = object3;
}
public void method1(){
    //不可達(方法運行完後可回收)
    Object object5 = o;//o不是GCRoots
    Object object6 = object5;
    Object object7 = object5;
}
//本地變量表中引用的對象
public void stack(){
    Object ostack =new Object();    //本地變量表的對象
    Object object9 = ostack;
    //以上object9 在方法沒有(運行完)出棧前都是可達的
}


}
複製代碼

咱們來畫個圖理解一下上面的代碼:

在這裏插入圖片描述

2.3 再談引用

不管是經過引用計數算法判斷對象的引用數量,仍是經過可達性分析算法判斷對象的引用鏈是否可達,判斷對象是否存活都與引用有關,那麼就讓咱們再次來談一談引用。

▶強引用

強引用就是指在程序代碼中廣泛存在的,相似於**「Object obj = new Object() 」**這類的就是強引用。

只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象

▶軟引用

軟引用是用來描述 一些有用可是並不是必需 的對象。

用軟引用關聯的對象,系統將要發生OOM以前,這些對象就會被回收。

​ 聯想記憶順序:只有男人吃軟飯一說,沒有男人吃弱飯一說,若是很強的男人排第一,那吃軟飯的男人能夠排第二,吃弱飯都不是男人了,因此排第三,最後是虛引用。

☛用一個栗子來講明軟引用的使用(PS: VM參數配置爲 -Xms10m -Xmx10m -XX:+PrintGC):

public class TestSoftRef {
    public static class User{
        public int id = 0;
        public String name = "";
        public User(int id, String name) {
            super();
            this.id = id;
            this.name = name;
        }
        @Override
        public String toString() {
            return "User [id=" + id + ", name=" + name + "]";
        }

    }

    public static void main(String[] args) {
        User u = new User(1,"鄭爽"); //new是強引用
        //軟引用的使用示例:
        SoftReference<User> userSoft = new SoftReference<User>(u);
        u = null;//幹掉強引用,確保這個實例只有userSoft的軟引用
        //--- 若是是 SoftReference<User> userSoft = new SoftReference<User>(new User()); 就無法幹掉強引用
        System.out.println(userSoft.get());
        System.gc();//進行一次GC垃圾回收
        System.out.println("After gc");
        System.out.println(userSoft.get());
        //往堆中填充數據,致使OOM
        List<byte[]> list = new LinkedList<>();
        try {
            for(int i=0;i<100;i++) {
                System.out.println("*************"+userSoft.get());
                list.add(new byte[1024*1024*1]); //1M的對象
            }
        } catch (Throwable e) {
            //拋出了OOM異常時打印軟引用對象
            System.out.println("Exception*************"+userSoft.get());
        }

    }
}
複製代碼

看下打印結果:

在這裏插入圖片描述

軟引用的使用場景:

例如,一個程序用來處理用戶提供的圖片。

若是將全部圖片讀入內存,這樣雖然能夠很快的打開圖片,但內存空間使用巨大,一些使用較少的圖片浪費內存空間,須要手動從內存中移除。

若是每次打開圖片都從磁盤文件中讀取到內存再顯示出來,雖然內存佔用較少,但一些常用的圖片每次打開都要訪問磁盤,代價巨大。

這個時候就能夠用軟引用構建緩存。

▶弱引用

一些有用(程度比軟引用更低)可是並不是必需,用弱引用關聯的對象,只能生存到下一次垃圾回收以前,GC發生時,無論內存夠不夠,都會被回收。

☛ Talk is cheap,show me the code!

public class TestWeakRef {
    public static class User{
        public int id = 0;
        public String name = "";
        public User(int id, String name) {
            super();
            this.id = id;
            this.name = name;
        }
        @Override
        public String toString() {
            return "User [id=" + id + ", name=" + name + "]";
        }

    }

    public static void main(String[] args) {
        User u = new User(1,"小爽");
        WeakReference<User> userWeak = new WeakReference<User>(u);
        u = null;//幹掉強引用,確保這個實例只有userWeak的弱引用
        System.out.println(userWeak.get());
        System.gc();//進行一次GC垃圾回收
        System.out.println("After gc");
        System.out.println(userWeak.get());
    }
}
複製代碼

打印結果分析:

在這裏插入圖片描述

注意: 軟引用 SoftReference和弱引用 WeakReference,能夠用在內存資源緊張的狀況下以及建立不是很重要的數據緩存。當系統內存不足的時候,緩存中的內容是能夠被釋放的。

實際運用(WeakHashMap、ThreadLocal

▶虛引用

幽靈引用,最弱,被垃圾回收的時候收到一個通知

3. 垃圾收集算法

3.1複製算法(Copying)

在這裏插入圖片描述

將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另一塊上面,而後再把已使用過的內存空間一次清理掉。這樣使得每次都是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等複雜狀況,只要按順序分配內存便可,實現簡單,運行高效。只是這種算法的代價是將內存縮小爲了原來的一半。


▶特色

▪ 實現簡單、運行高效

▪ 內存複製、沒有內存碎片

▪ 利用率只有一半


▶注意事項:

▪ 新生代 使用該算法

▪ 新生代中3個區的比例8:1:1

▪ 空間擔保


對8:1:1比例的說明:

新生代中的對象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)。 內存的分配擔保就比如咱們去銀行貸款,若是咱們信譽良好,在98%的狀況下都能按時償還,因而銀行可能會默認咱們下一次也能按時按量的償還貸款。只須要有一個擔保人能保證若是咱們不能還款時,能夠從他的帳戶扣錢,那銀行就認爲沒有風險了,內存的分配擔保也同樣,若是另外一塊survival空間沒有足夠空間存放,上一次新生代收集下來的存活對象時,這些對象將直接經過分配擔保機制進入老年代。

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

在這裏插入圖片描述

算法分爲「標記」和「清除」兩個階段:首先標記出全部須要回收的對象,在標記完成後統一回收全部被標記的對象。

它的主要不足空間問題,標記清除以後會產生大量不連續的內存碎片,空間碎片太多可能會致使之後在程序運行過程當中須要分配較大對象時,沒法找到足夠的連續內存而不得不提早觸發另外一次垃圾收集動做。


▶特色

▪ 利用率百分之百

▪ 不須要內存複製

▪ 有內存碎片

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

在這裏插入圖片描述
首先標記出全部須要回收的對象,在標記完成後,後續步驟不是直接對可回收對象進行清理,而是讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存。


▶特色

▪ 利用率百分之百

▪ 沒有內存碎片

▪ 須要內存複製

▪ 效率通常般

4.堆內存分配策略

▶對象優先在Eden分配

若是Eden內存空間不足,就會發生Minor GC

▶大對象直接進入老年代

大對象:須要大量連續內存空間的Java對象,好比很長的字符串和大型數組,

大對象對虛擬機的內存分配來講是一個壞消息(∵一、大對象容易致使內存還有很多空間時,就提早觸發垃圾收集以獲取足夠的連續空間來「安置」它們)。

比遇到一個大對象更加壞的消息就是遇到一羣「朝生夕滅」的「短命大對象」(∵ 二、會進行大量的內存複製)。

虛擬機提供了一個 -XX:PretenureSizeThreshold 參數 ,

大於這個數量直接在老年代分配,缺省爲0 ,表示毫不會直接分配在老年代。

▶長期存活的對象將進入老年代

默認15歲,-XX:MaxTenuringThreshold 參數可調整

▶動態對象年齡斷定

爲了能更好地適應不一樣程序的內存情況,虛擬機並非永遠地要求對象的年齡必須達到了MaxTenuringThreshold才能晉升老年代,若是在Survivor空間中相同年齡全部對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象就能夠直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡。

▶空間分配擔保

新生代中有大量的對象存活,survivor空間不夠,當出現大量對象在MinorGC後仍然存活的狀況(最極端的狀況就是內存回收後新生代中全部對象都存活),就須要老年代進行分配擔保,把Survivor沒法容納的對象直接進入老年代.只要老年代的連續空間大於新生代對象的總大小或者歷次晉升的平均大小,就進行Minor GC,不然FullGC。

因此,新生代通常不會內存溢出,由於有老年代作擔保。

相關文章
相關標籤/搜索