JVM內存模型及垃圾回收

定義

通俗的來講,jvm主要分爲5個部分  程序計數器、虛擬機枝、本地方法枝、 Java 堆、 方法區, 引用大佬總結的概括程序計數器用於存放下一條運行的指令,虛擬機棧和本地方法棧用於存放函數調用堆棧信息, Java 堆用於存放 Java 程序運行時所需的對象等數據,方法區用於存放程序的類元數據信息 。 

       

程序計數器: 是一塊很小的內存空間,每個線程私有,可以看作當前線程程序執行的字節碼的行號提示器。

: 線程私有存儲空間,訪問速度僅次於寄存器,棧裏面的存儲單位爲,棧幀, 棧幀對應着方法, 存放着方法的 局部變量表,操作數棧,常量池引用,方法返回地址。

本地方法棧:用於管理本地方法的調用,本地方法一般都是由C語言編寫,調用本地方法,都會使用本地方法棧, 而不是像 自己定義的方法那樣在虛擬機的棧中創建棧幀,而是java棧動態連接到本地方法棧。

方法區:   線程之間共享的,儲存類的類型信息,常量池,方法信息,JIT編譯後的代碼等數據等, jdk1.7後  對方法區,的位置都由改變, jdk 1.7以前, 常量池在永久代中, 從1.7 以後就搬入到堆中, 方便對其進行垃圾回收, jdk1.8以後永久代就被元空間取代;

: 所有線程之間共享的,首先是默認分爲 新生代和老年代,默認佔比是1:2,新生代有分爲eden區和survivor(分爲 from 和to) 默認比例爲 8:1;

垃圾回收算法

垃圾標記算法有,引用計數法,和根搜索算法(可達性分析)等,常見的垃圾收集算法有 標記-清除算法( Mark-Sweep ),複製算法(Copying ),標記-整理算法(Mark-Compact )

引用計數法

引用計數法( Reference Counting)在 GC 執行垃圾回收之前,首先需要區分出內存中哪些
是存活對象,哪些是已經死亡的對象。只有被標記爲己經死亡的對象, GC 纔會在執行垃圾回
收時,釋放掉其所佔用的內存空間 , 因此這個過程我們可以稱爲垃圾標記階段。

對於對象A來說,只要有任意一個對象引用了A,A的引用計數器就加1, 當引用失效時,計數器就減一,當對象A的計數器的值爲0時,就可以被回收,  但是有個明顯的問題,當存在對象A,對象B,它們之間互相引用,導致引用計數都不爲0,就不能被刪除掉。

根搜索算法

以根對象集合爲起始點,按照從上至下的方式搜索被根對象集合所連接的目標對象是否可達(使用根搜索算法後,內存中
的存活對象都會被根對象集合直接或間接連接着),如果目標對象不可達,就意味着該對象己經死亡,便可以將其標記爲垃圾對象。在根搜索算法中,只有能夠被根對象集合直接或者間接連接的對象纔是存活對象, 當對象被標記爲不可達,並不意味着會被馬上清除掉,一個對象的死亡,至少要經歷兩次標記過程。 第一次是執行對象的finalize()將對象放入F-Queue 中, 之後再進行一次判斷,纔會被清除掉

當對象放入F-queue時候,只要將對象,與任何引用關聯上,就可以使對象重新存活,

public class FinalizeEscapeGC {
    public static FinalizeEscapeGC SAVE_HOOK = null;
    public void isAlive() {
        System.out.println("yes, i am still alive");
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize mehtod executed!");
        FinalizeEscapeGC.SAVE_HOOK = this;
    }

    public static void main(String[] args) throws Throwable {
        SAVE_HOOK = new FinalizeEscapeGC();
        //對象第一次成功拯救自己       
        SAVE_HOOK = null;
        System.gc();
        // 因爲Finalizer方法優先級很低,暫停0.5秒,以等待它       
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no, i am dead");
        }
        // 下面這段代碼與上面的完全相同,但是這次自救卻失敗了       
        SAVE_HOOK = null;
        System.gc();
        // 因爲Finalizer方法優先級很低,暫停0.5秒,以等待它       
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no, i am dead");
        }
    }
}

(對象的finalize方法只會被執行一次,第二次對象就沒有被放入到F-queue 直接被回收了)執行的結果爲:

finalize mehtod executed!
yes, i am still alive
no, i am dead

標記-清除算法

算法分爲“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成後統一回收所有被標記的對象。

複製算法

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

標記-整理算法

標記過程仍然與“標記-清除”算法一樣,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動整理壓縮,然後直接清理掉端邊界以外的內存。

 

增量算法

垃圾回收的過程都需要暫停應用程序,如果垃圾回收暫停了很長時間,就會影響用戶的體驗,因此增量算法的思想,就是用多線程的方式,一邊進行標記-清除,複製,清理垃圾, 一邊執行應用程序, 這樣就減少了系統的停頓時間;

分代收集算法

將堆細分爲年輕代,和老年代,根據不同代中對象的使用情況不用, 分別使用不同垃圾收集器, 年輕代中,對象大多數屬於朝生夕死的,使用效率較高的複製算法, 老年代對象大多是都是經歷過多次垃圾回收倖存的對象, 在加上老年代回收性價要比年輕代低,使用標記整理算法。(看到分代收集器,是不是突然明白了,爲啥jvm的堆,要分爲年輕代,老年代,年輕代 有爲啥有兩個survivor區 )

 

博客筆記,參考了 《深入理解JVM & G1 GC》和 《深入理解Java虛擬機_JVM高級特性與最佳實踐》  安利一下

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