欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入理解JVM垃圾回收算法

 更新時間:2021年06月16日 14:25:58   作者:codeHaogg  
我們都知道java語言與C語言最大的區(qū)別就是內(nèi)存自動回收,那么JVM是怎么控制內(nèi)存回收的,這篇文章將介紹JVM垃圾回收的幾種算法,從而了解內(nèi)存回收的基本原理

一、垃圾標記階段

  • 對象存活判斷:在堆里存放著幾乎所有的Java對象實例,在GC執(zhí)行垃圾回收之前,首先需要區(qū)分出內(nèi)存中哪些是存活對象,哪些是已經(jīng)死亡的對象。只有被標記為己經(jīng)死亡的對象,GC才會在執(zhí)行垃圾回收時,釋放掉其所占用的內(nèi)存空間,因此這個過程我們可以稱為垃圾標記階段。
  • 那么在JVM中究竟是如何標記一個死亡對象呢?簡單來說,當一個對象已經(jīng)不再被任何的存活對象繼續(xù)引用時,就可以宣判為已經(jīng)死亡。
  • 判斷對象存活一般有兩種方式:引用計數(shù)算法和可達性分析算法。

1.1、引用計數(shù)法 (java沒有采用)

引用計數(shù)算法(Reference Counting)比較簡單,對每個對象保存一個整型 的引用計數(shù)器屬性。用于記錄對象被引用的情況。

對于一個對象A,只要有任何一個對象引用了A,則A的引用計數(shù)器就加1;當引用失效時,引用計數(shù)器就減1。只要對象A的引用計數(shù)器的值為0,即表示對象A不可能再被使用,可進行回收。

優(yōu)點:

  • 實現(xiàn)簡單,垃圾對象便于辨識;判定效率高,回收沒有延遲性。

缺點:

  • 它需要單獨的字段存儲計數(shù)器,這樣的做法增加了存儲空間的開銷。
  • 每次賦值都需要更新計數(shù)器,伴隨著加法和減法操作,這增加了時間開銷。
  • 引用計數(shù)器有一個嚴重的問題,即無法處理循環(huán)引用的情況。這是一 條致命缺陷,導致在Java的垃圾回收器中沒有使用這類算法。

循環(huán)引用導致的內(nèi)存泄漏情況:

證明:java使用的不是引用計數(shù)算法示例代碼:

/**
 * -XX:+PrintGCDetails
 * 證明:java使用的不是引用計數(shù)算法
 */
public class RefCountGC {
    //這個成員屬性唯一的作用就是占用一點內(nèi)存
    private byte[] bigSize = new byte[5 * 1024 * 1024];//5MB,僅僅意味著只要創(chuàng)建我這個RefCountGC對象實例,就會占用5M的堆空間

    Object reference = null;

    public static void main(String[] args) {
        RefCountGC obj1 = new RefCountGC();
        RefCountGC obj2 = new RefCountGC();

        obj1.reference = obj2;
        obj2.reference = obj1;

        obj1 = null;
        obj2 = null;
        //顯式的執(zhí)行垃圾回收行為
        //這里發(fā)生GC,obj1和obj2能否被回收?能,證明Java沒有采用引用計數(shù)算法
        System.gc();

        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

引用計數(shù)法的弊端:即使把obj1 一reference和obj2 一reference(棧引用)置null。 則在Java堆當中的兩塊內(nèi)存依然保持著互相引用,無法回收。

但是把obj1 一reference和obj2 一reference置為null之后,如果顯示的去調(diào)用System.gc();這里將會發(fā)生GC,回收堆中bj1和obj2實體。從而證明Jlava沒有采用引用計數(shù)算法 .

1.2、可達性分析算法

也叫根搜索算法或追蹤性垃圾收集

  • 相對于引用計數(shù)算法而言,可達性分析算法不僅同樣具備實現(xiàn)簡單和執(zhí)行高 效等特點,更重要的是該算法可以有效地解決在引用計數(shù)算法中循環(huán)引用的問題,防止內(nèi)存泄漏的發(fā)生。
  • 相較于引用計數(shù)算法,這里的可達性分析就是Java、C#選擇的。這種類型的垃圾收集通常也叫作追蹤性垃圾收集(Tracing GarbageCollection)。
  • 所謂"GC Roots"根集合就是一組必須活躍的引用。
  • 可達性分析算法是以根對象集合(GCRoots)為起始點,按照從上至下的方式搜索被根對象集合所連接的目標對象是否可達。
  • 使用可達性分析算法后,內(nèi)存中的存活對象都會被根對象集合直接或間接連接著,搜索所走過的路徑稱為引用鏈(Reference Chain)
  • 如果目標對象沒有任何引用鏈相連,則是不可達的,就意味著該對象己經(jīng)死亡,可以標記為垃圾對象。

GC Roots

在Java語言中,GC Roots對象包括以下幾類元素:

1.虛擬機棧中引用的對象:比如:各個線程當中被調(diào)用的方法中使用到的參數(shù)、局部變量等。

2.本地方法棧內(nèi)JNI(通常說的本地方法)引用的對象

3.方法區(qū)中類靜態(tài)屬性引用的對象➢比如:Java類的引用類型靜態(tài)變量

Class Dog {
    private static Object tail;
} 

4.方法區(qū)中常量引用的對象:➢比如:字符串常量池(string Table) 里的引用

Class Dog {
    private final Object tail;
} 

5.所有被同步鎖synchroni zed持有的對象

6.Java虛擬機內(nèi)部的引用:➢基本數(shù)據(jù)類型對應(yīng)的Class對象,一些常駐的異常對象(如: NullPointerException、OutOfMemoryError) ,系統(tǒng)類加載器。

7.反映java虛擬機內(nèi)部情況的JMXBean、JVMTI中注冊的回調(diào)、本地代碼緩存等.

8.除了這些固定的GCRoots集合以外,根據(jù)用戶所選用的垃圾收集器以及當 前回收的內(nèi)存區(qū)域不同,還可以有其他對象“臨時性”地加入,共同構(gòu)成完整GC Roots集合。比如:分代收集和局部回收(Partial GC)。➢如果只針對Java堆中的某一塊區(qū)域進行垃圾回收(比如:典型的只針 對新生代),必須考慮到內(nèi)存區(qū)域是虛擬機自己的實現(xiàn)細節(jié),更不是孤立封閉的,這個區(qū)域的對象完全有可能被其他區(qū)域的對象所引用,這時候就需要一.并將關(guān)聯(lián)的區(qū)域?qū)ο笠布尤隚C Roots集合中去考慮,才能保證可達性分析的準確性。

9.小技巧:由于Root采用棧方式存放變量和指針,所以如果一個指針,它保存了堆內(nèi)存里面的對象,但是自己又不存放在堆內(nèi)存里面,那它就是一個Root

注意

  • 如果要使用可達性分析算法來判斷內(nèi)存是否可回收,那么分析工作必須在 一個能保障一致性的快照中進行。這點不滿足的話分析結(jié)果的準確性就無法保證。
  • 即使是號稱(幾乎)不會發(fā)生停頓的CMS收集器中,枚舉根節(jié)點時也是必須要停頓的。這點也是導致GC進行時必須“StopTheWorld"的一個重要原因。

二、對象的finalization機制

Java語言提供了對象終止(finalization)機制來允許開發(fā)人員提供對象被銷毀之前的自定義處理邏輯。

當垃圾回收器發(fā)現(xiàn)沒有引用指向一個對象,即:垃圾回收此對象之前,總會先調(diào)用這個對象的finalize()方法。

finalize()方法允許在子類中被重寫,用于在對象被回收時進行資源釋放。通常在這個方法中進行一些資源釋放和清理的工作,比如關(guān)閉文件、套接字和數(shù)據(jù)庫連接等。

應(yīng)該交給垃圾回收機制調(diào)用,永遠不要主動調(diào)用某個對象的finalize ()方法。理由包括下面幾點:

  • 意思是如果你重寫了finalize(),finalize()會在一個低優(yōu)先級的線程中等待自動執(zhí)行(前提是發(fā)生了GC),不需要人為主動的去調(diào)用。
  • 在finalize() 時可能會導致對象復(fù)活。
  • finalize()方法的執(zhí)行時間是沒有保障的,它完全由Gc線程決定,極端情況下,若不發(fā)生GC,則finalize() 方法將沒有執(zhí)行機會。
  • 一個糟糕的finalize ()會嚴重影響GC的性能。

從功能上來說,finalize()方法與C++ 中的析構(gòu)函數(shù)比較相似,但是Java采用的是基于垃圾回收器的自動內(nèi)存管理機制,所以finalize()方法在本質(zhì),上不同于C++ 中的析構(gòu)函數(shù)。

2.1、對象是否"死亡"

由于finalize ()方法的存在,虛擬機中的對象一般處于三種可能的狀態(tài)

如果從所有的根節(jié)點都無法訪問到某個對象,說明對象己經(jīng)不再使用了。一般來說,此對象需要被回收。但事實上,也并非是“非死不可”的,這時候它們暫時處于“緩刑”階段。一個無法觸及的對象有可能在某一個條件下“復(fù)活”自己,如果這樣,那么對它的回收就是不合理的,為此,定義虛擬機中的對象可能的三種狀態(tài)。如下:

  • 可觸及的:從根節(jié)點開始,可以到達這個對象。
  • 可復(fù)活的:對象的所有引用都被釋放,但是對象有可能在finalize()中復(fù)活。
  • 不可觸及的:對象的finalize()被調(diào)用,并且沒有復(fù)活,那么就會進入不可觸及狀態(tài)。不可觸及的對象不可能被復(fù)活,因為finalize() 只會被調(diào)用1次。

以上3種狀態(tài)中,是由于finalize()方法的存在,進行的區(qū)分。只有在對象不可觸及時才可以被回收。 判定是否可以回收具體過程 判定一個對象objA是否可回收,至少要經(jīng)歷兩次標記過程:

  • 過程1.如果對象objA到GC Roots沒有引用鏈,則進行第一 次標記。
  • 過程2.進行篩選,判斷此對象是否有必要執(zhí)行finalize()方法

①如果對 象objA沒有重寫finalize()方法,或者finalize ()方法已經(jīng)被虛擬機調(diào)用過,則虛擬機視為“沒有必要執(zhí)行”,objA被判定為不可觸及的。

②如果對象objA重寫了finalize()方法,且還未執(zhí)行過,那么objA會被插入到F一Queue隊列中,由一個虛擬機自動創(chuàng)建的、低優(yōu)先級的Finalizer線程觸發(fā)其finalize()方法執(zhí)行。

③finalize()方法是對象逃脫死亡的最后機會,稍后Gc會對F一Queue隊列中的對象進行第二次標記。如果objA在finalize()方法中與引用鏈上的任何一個對象建立了聯(lián)系,那么在第二次標記時,objA會被移出“即將回收”集合。之后,對象會再次出現(xiàn)沒有引用存在的情況。在這個情況下,finalize方法不會被再次調(diào)用,對象會直接變成不可觸及的狀態(tài),也就是說,一個對象的finalize方法只會被調(diào)用一次。

代碼測試對象可復(fù)活:

/**
 * 測試Object類中finalize()方法,即對象的finalization機制。
 *
 */
public class CanReliveObj {
    public static CanReliveObj obj;//類變量,屬于 GC Root


    //此方法只能被調(diào)用一次
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("調(diào)用當前類重寫的finalize()方法");
        obj = this;//當前待回收的對象在finalize()方法中與引用鏈上的一個對象obj建立了聯(lián)系
    }


    public static void main(String[] args) {
        try {
            obj = new CanReliveObj();
            // 對象第一次成功拯救自己
            obj = null;
            System.gc();//調(diào)用垃圾回收器
            System.out.println("第1次 gc");
            // 因為Finalizer線程優(yōu)先級很低,暫停2秒,以等待它
            Thread.sleep(2000);
            if (obj == null) {
                System.out.println("obj is dead");
            } else {
                System.out.println("obj is still alive");
            }
            System.out.println("第2次 gc");
            // 下面這段代碼與上面的完全相同,但是這次自救卻失敗了
            obj = null;
            System.gc();
            // 因為Finalizer線程優(yōu)先級很低,暫停2秒,以等待它
            Thread.sleep(2000);
            if (obj == null) {
                System.out.println("obj is dead");
            } else {
                System.out.println("obj is still alive");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

控制臺輸出:

第1次 gc

調(diào)用當前類重寫的finalize()方法

obj is still alive

第2次 gc

obj is dead

三、使用(MAT與JProfiler)工具分析GCRoots

MAT是Memory Analyzer的簡稱,它是一 款功能強大的Java堆內(nèi)存分析器。用于查找內(nèi)存泄漏以及查看內(nèi)存消耗情況。

MAT是基于Eclipse開發(fā)的,是一款免費的性能分析工具。

可以在http://www.eclipse.org/mat/下載并使用MAT。

3.1、獲取dump文件

方式1: 命令行使用jmap

jps

jmap -dump:format=b,live,file=test1.bin {進程id}

方式2:使用JVisualVM導出

捕獲的heap dump文件是一個臨時文件,關(guān)閉JVisua1VM后自動刪除,若要保留,需要將其另存為文件。

可通過以下方法捕獲heap dump:

  • 在左側(cè)“Application”(應(yīng)用程序)子窗口中右擊相應(yīng)的應(yīng)用程序,選擇Heap Dump(堆Dump)。
  • 在Monitor (監(jiān)視)子標簽頁中點擊Heap Dump (堆Dump)按鈕。

本地應(yīng)用程序的Heap dumps作為應(yīng)用程序標簽頁的一個子標簽頁打開。同時, heap dump在左側(cè)的Application (應(yīng)用程序)欄中對應(yīng)一個含有時間戳的節(jié)點。右擊這個節(jié)點選擇save as (另存為)即可將heap dump保存到本地。

3.2、GC Roots分析

public class GCRootsTest {
    public static void main(String[] args) {
        List<Object> numList = new ArrayList<>();
        Date birth = new Date();

        for (int i = 0; i < 100; i++) {
            numList.add(String.valueOf(i));
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("數(shù)據(jù)添加完畢,請操作:");
        new Scanner(System.in).next();
        numList = null;
        birth = null;

        System.out.println("numList、birth已置空,請操作:");
        new Scanner(System.in).next();

        System.out.println("結(jié)束");
    }
}

使用MAT查看GC Roots:

使用jProfiler進行GC溯源:

使用Jprofiler分析OOM:

/**
 * -Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
 *
 */
public class HeapOOM {
    byte[] buffer = new byte[1 * 1024 * 1024];//1MB

    public static void main(String[] args) {
        ArrayList<HeapOOM> list = new ArrayList<>();

        int count = 0;
        try{
            while(true){
                list.add(new HeapOOM());
                count++;
            }
        }catch (Throwable e){
            System.out.println("count = " + count);
            e.printStackTrace();
        }
    }
}

控制臺輸出:

java.lang.OutOfMemoryError: Java heap space

Dumping heap to java_pid45386.hprof ...

Heap dump file created [7390812 bytes in 0.019 secs]

count = 6

java.lang.OutOfMemoryError: Java heap space

    at com.dsh.jvm.gc.algorithm.HeapOOM.<init>(HeapOOM.java:12)

    at com.dsh.jvm.gc.algorithm.HeapOOM.main(HeapOOM.java:20)

對應(yīng)count=6

出現(xiàn)OOM的代碼

四、垃圾清除階段

當成功區(qū)分出內(nèi)存中存活對象和死亡對象后,GC接下來的任務(wù)就是執(zhí)行垃圾回收,釋放掉無用對象所占用的內(nèi)存空間,以便有足夠的可用內(nèi)存空間為新對象分配內(nèi)存。
目前在JVM中比較常見的三種垃圾收集算法是標記一清除算法( Mark一Sweep)、復(fù)制算法(Copying)、標記一壓縮算法(Mark一Compact)。

4.1、標記-清除算法

標記一清除算法(Mark一Sweep)是一種非常基礎(chǔ)和常見的垃圾收集算法,該算法被J . McCarthy等人在1960年提出并并應(yīng)用于Lisp語言。

當堆中的有效內(nèi)存空間(available memory) 被耗盡的時候,就會停止整個程序(也被稱為stop the world),然后進行兩項工作,第一項則是標記,第二項則是清除。

  • 標記: Collector從引用根節(jié)點開始遍歷,標記所有被引用的對象。一般是在對象的Header中記錄為可達對象。
  • 清除: Collector對堆 內(nèi)存從頭到尾進行線性的遍歷,如果發(fā)現(xiàn)某個對象在其Header中沒有標記為可達對象,則將其回收。

標記清除算法的缺點:

  • 效率不算高
  • 在進行Gc的時候,需要停止整個應(yīng)用程序,導致用戶體驗差。
  • 這種方式清理出來的空閑內(nèi)存是不連續(xù)的,產(chǎn)生內(nèi)存碎片。需要維護一個空閑列表。

注意:何為清除?

這里所謂的清除并不是真的置空,而是把需要清除的對象地址保存在空閑 的地址列表里。下次有新對象需要加載時,判斷垃圾的位置空間是否夠,如果夠,就存放。

4.2、復(fù)制算法

為了解決標記一清除算法在垃圾收集效率方面的缺陷,M.L.Minsky于1963年發(fā)表了著名的論文,“ 使用雙存儲區(qū)的Li sp語言垃圾收集器CALISP Garbage Collector Algorithm Using SerialSecondary Storage )”。M.L. Minsky在該論文中描述的算法被人們稱為復(fù)制(Copying)算法,它也被M. L.Minsky本人成功地引入到了Lisp語言的一個實現(xiàn)版本中。

將活著的內(nèi)存空間分為兩塊,每次只使用其中一塊,在垃圾回收時將正在使用的內(nèi)存中的存活對象復(fù)制到未被使用的內(nèi)存塊中,之后清除正在使用的內(nèi)存塊中的所有對象,交換兩個內(nèi)存的角色,最后完成垃圾回收。堆中S0和S1使用的就是復(fù)制算法。

復(fù)制算法優(yōu)缺點:

  • 沒有標記和清除過程,實現(xiàn)簡單,運行高效
  • 復(fù)制過去以后保證空間的連續(xù)性,不會出現(xiàn)“碎片”問題。
  • 此算法的缺點也是很明顯的,就是需要兩倍的內(nèi)存空間。
  • 對于G1這種分拆成為大量region的GC,復(fù)制而不是移動,意味著GC需要維護region之間對象引用關(guān)系,不管是內(nèi)存占用或者時間開銷也不小。
  • 如果系統(tǒng)中的垃圾對象很多,復(fù)制算法需要復(fù)制的存活對象數(shù)量并不會太大。因此在真正需要垃圾回收的時刻,復(fù)制算法的效率是很高的。
  • 又由于對象在垃圾回收過程中統(tǒng)一被復(fù)制到新的內(nèi)存空間中,因此,可確?;厥蘸蟮膬?nèi)存空間是沒有碎片的。該算法的缺點是將系統(tǒng)內(nèi)存折半。

應(yīng)用場景:

在新生代,對常規(guī)應(yīng)用的垃圾回收,一次通??梢曰厥?08一 99的內(nèi)存空間?;厥招詢r比很高。所以現(xiàn)在的商業(yè)虛擬機都是用這種收集算法回收新生代。

4.3、標記-壓縮(整理,Mark-Compact)算法

復(fù)制算法的高效性是建立在存活對象少、垃圾對象多的前提下的。這種情況在新生代經(jīng)常發(fā)生。

但是在老年代,更常見的情況是大部分對象都是存活對象。如果依然使用復(fù)制算法,由于存活對象較多,復(fù)制的成本也將很高。

因此,基于老年代垃圾回收的特性,需要使用其他的算法。標記一清除算法的確可以應(yīng)用在老年代中,但是該算法不僅執(zhí)行效率低下,而且在執(zhí)行完內(nèi)存回收后還會產(chǎn)生內(nèi)存碎片,所以JVM的設(shè)計者需要在此基礎(chǔ)之上進行改進。標記一壓縮(Mark一Compact) 算法由此誕生。1970年前后,G. L. Steele 、C. J. Chene和D.S. Wise 等研究者發(fā)布標記一壓縮算法。在許多現(xiàn)代的垃圾收集器中,人們都使用了標記一壓縮算法或其改進版本。

標記壓縮算法執(zhí)行過程:

  • 第一階段和標記一清除算法一樣,從根節(jié)點開始標記所有被引用對象.
  • 第二階段將所有的存活對象壓縮到內(nèi)存的一端,按順序排放。
  • 之后,清理邊界外所有的空間

標記一壓縮算法的最終效果等同于標記一清除算法執(zhí)行完成后,再進行一次內(nèi)存碎片整理,因此,也可以把它稱為標記一清除一壓縮(Mark一 Sweep一Compact)算法。

二者的本質(zhì)差異在于標記一清除算法是一種非移動式的回收算法,標記一壓.縮是移動式的。是否移動回收后的存活對象是一項優(yōu)缺點并存的風險決策。

可以看到,標記的存活對象將會被整理,按照內(nèi)存地址依次排列,而未被標記的內(nèi)存會被清理掉。

如此一來,當我們需要給新對象分配內(nèi)存時,JVM只需要持有一個內(nèi)存的起始地址即可,這比維護一個空閑列表顯然少了許多開銷。

指針碰撞(Bump the Pointer )

如果內(nèi)存空間以規(guī)整和有序的方式分布,即已用和未用的內(nèi)存都各自一邊,彼此之間維系著一個記錄下一次分配起始點的標記指針,當為新對象分配內(nèi)存時,只需要通過修改指針的偏移量將新對象分配在第一個空閑內(nèi)存位置上,這種分配方式就叫做指針碰撞(Bump the Pointer) 。

標記壓縮算法優(yōu)點:

  • 消除了標記一清除算法當中,內(nèi)存區(qū)域分散的缺點。
  • 我們需要給新對象分配內(nèi)存時,JVM只 需要持有一個內(nèi)存的起始地址即可。
  • 消除了復(fù)制算法當中,內(nèi)存減半的高額代價。

標記壓縮算法缺點:

  • 從效率.上來說,標記一整理算法要低于復(fù)制算法。
  • 移動對象的同時,如果對象被其他對象引用,則還需要調(diào)整引用的地址。· 移動過程中,需要全程暫停用戶應(yīng)用程序。即: STW

4.4、以上三種垃圾回收算法對比

  • 速度上來說,復(fù)制算法是當之無愧的老大,但是卻浪費了太多內(nèi)存。
  • 而為了盡量兼顧上面提到的三個指標,標記一整理算法相對來說更平滑一些,但是效率/速度上不盡如人意,它比復(fù)制算法多了一個標記的階段,比標記一清除多了一個整理內(nèi)存的階段。

4.5、分代收集算法

難道就沒有一種最優(yōu)的算法么?沒有最好的算法,只有更合適的算法。前面所有這些算法中,并沒有一種算法可以完全替代其他算法,它們都具有自己獨特的優(yōu)勢和特點。分代收集算法應(yīng)運而生。、

分代收集算法,是基于這樣一個事實:不同的對象的生命周期是不一樣的。因此,不同生命周期的對象可以采取不同的收集方式,以便提高回收效率。一般是把Java堆分為新生代和老年代,這樣就可以根據(jù)各個年代的特點使用不同的回收算法,以提高垃圾回收的效率。

在Java程序運行的過程中,會產(chǎn)生大量的對象,其中有些對象是與業(yè)務(wù)信息相關(guān),比如Http請求中的Session對象、線程、Socket連接, 這類對象跟業(yè)務(wù)直接掛鉤,因此生命周期比較長。

但是還有一些對象,主要是程序運行過程中生成的臨時變量,這些對象生命周期會比較短,比如: String對象, 由于其不變類的特性,系統(tǒng)會產(chǎn)生大量的這些對象,有些對象甚至只用一次即可回收。

目前幾乎所有的GC都是采用分代收集(Generational Collecting) 算法執(zhí)行垃圾回收的。在HotSpot中,基于分代的概念,GC所使用的內(nèi)存回收算法必須結(jié)合年輕代和老年代各自的特點。

年輕代(Young Gen)

  • 年輕代特點:區(qū)域相對老年代較小,對象生命周期短、存活率低,回收頻繁。
  • 這種情況復(fù)制算法的回收整理,速度是最快的。復(fù)制算法的效率只和當前存活對象大小有關(guān),因此很適用于年輕代的回收。而復(fù)制算法內(nèi)存利用率不高的問題,通過hotspot中的兩個survivor的設(shè)計得到緩解(伊甸園:幸存者=8:1)?!?/li>

老年代(Tenured Gen)

  • 老年代特點:區(qū)域較大,對象生命周期長、存活率高,回收不及年輕代頻繁。
  • 這種情況存在大量存活率高的對象,復(fù)制算法明顯變得不合適。一般是由標記一清除或者是標記一清除與標記一整理的混合實現(xiàn)。Mark階段的開銷與存活對象的數(shù)量成正比。Sweep階段的開銷與所管理區(qū)域的大小成正相關(guān)。Compact階段的開銷與存活對象的數(shù)據(jù)成正比。

以HotSpot中的CMS回收器為例,CMS是基于Mark一 Sweep實現(xiàn)的,對于對象的回收效率很高。而對于碎片問題,CMS采用基于Mark一Compact算法的Serial 0ld回收器作為補償措施:當內(nèi)存回收不佳(碎片導致的Concurrent Mode Failure時),將采用Serial 0ld執(zhí)行Full GC以達到對老年代內(nèi)存的整理。

分代的思想被現(xiàn)有的虛擬機廣泛使用。幾乎所有的垃圾回收器都區(qū)分新生代和老年代。

4.6、增量收集算法

上述現(xiàn)有的算法,在垃圾回收過程中,應(yīng)用軟件將處于一種stop the World的狀態(tài)。在Stop the World狀態(tài)下,應(yīng)用程序所有的線程都會掛起,暫停一切正常的工作,等待垃圾回收的完成。如果垃圾回收時間過長,應(yīng)用程序會被掛起很久,將嚴重影響用戶體驗或者系統(tǒng)的穩(wěn)定性。為了解決這個問題,即對實時垃圾收集算法的研究直接導致了增量收集(Incremental Collecting) 算法的誕生。

增量收集算法基本思想

如果一次性將所有的垃圾進行處理,需要造成系統(tǒng)長時間的停頓,那么就可以讓垃圾收集線程和應(yīng)用程序線程交替執(zhí)行。每次,垃圾收集線程只收集一小片區(qū)域的內(nèi)存空間,接著切換到應(yīng)用程序線程。依次反復(fù),直到垃圾收集完成。
總的來說,增量收集算法的基礎(chǔ)仍是傳統(tǒng)的標記一清除和復(fù)制算法。增量收集算法通過對線程間沖突的妥善處理,允許垃圾收集線程以分階段的方式完成標記、清理或復(fù)制工作。

增量收集算法優(yōu)缺點:

使用這種方式,由于在垃圾回收過程中,間斷性地還執(zhí)行了應(yīng)用程序代碼,所以能減少系統(tǒng)的停頓時間。

但是,因為線程切換和上下文轉(zhuǎn)換的消耗,會使得垃圾回收的總體成本上升,造成系統(tǒng)吞吐量的下降。

4.7、分區(qū)算法G1回收器

一般來說,在相同條件下,堆空間越大,一次GC時所需要的時間就越長,有關(guān)GC產(chǎn)生的停頓也越長。

為了更好地控制GC產(chǎn)生的停頓時間,將一塊大的內(nèi)存區(qū)域分割成多個小塊,根據(jù)目標的停頓時間,每次合理地回收若干個小區(qū)間,而不是整個堆空間,從而減少一次GC所產(chǎn)生的停頓。

分代算法將按照對象的生命周期長短劃分成兩個部分,而分區(qū)算法將整個堆空間劃分成連續(xù)的不同小區(qū)間。

每一個小區(qū)間都獨立使用,獨立回收。這種算法的好處是可以控制一次回收多少個小區(qū)間。

以上就是深入理解JVM垃圾回收算法的詳細內(nèi)容,更多關(guān)于JVM垃圾回收算法的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • java中HashSet的特點及實例用法

    java中HashSet的特點及實例用法

    在本篇文章里小編給大家整理的是一篇關(guān)于java中HashSet的特點及實例用法,有興趣的朋友們可以學習下。
    2021-04-04
  • JAVA自定義異常使用方法實例詳解

    JAVA自定義異常使用方法實例詳解

    這篇文章主要介紹了JAVA自定義異常使用方法實例詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-06-06
  • springboot 配置日志 打印不出來sql的解決方法

    springboot 配置日志 打印不出來sql的解決方法

    這篇文章主要介紹了springboot 配置日志 打印不出來sql的解決方法,幫助大家更好的理解和使用springboot框架,感興趣的朋友可以了解下
    2020-11-11
  • mybatis Reflector反射類的具體使用

    mybatis Reflector反射類的具體使用

    Reflector類是MyBatis反射模塊的核心,負責處理類的元數(shù)據(jù),以實現(xiàn)屬性與數(shù)據(jù)庫字段之間靈活映射的功能,本文主要介紹了mybatis Reflector反射類的具體使用,感興趣的可以了解一下
    2024-02-02
  • Java使用easypoi快速導入導出的實現(xiàn)

    Java使用easypoi快速導入導出的實現(xiàn)

    這篇文章主要介紹了實現(xiàn)Java使用easypoi快速導入導出的實現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-03-03
  • 如何使用Spring+redis實現(xiàn)對session的分布式管理

    如何使用Spring+redis實現(xiàn)對session的分布式管理

    本篇文章主要介紹了如何使用Spring+redis實現(xiàn)對session的分布式管理,本文主要是在Spring中實現(xiàn)分布式session,采用redis對session進行持久化管理,感興趣的小伙伴們可以參考一下
    2018-06-06
  • java與JSON數(shù)據(jù)的轉(zhuǎn)換實例詳解

    java與JSON數(shù)據(jù)的轉(zhuǎn)換實例詳解

    這篇文章主要介紹了java與JSON數(shù)據(jù)的轉(zhuǎn)換實例詳解的相關(guān)資料,需要的朋友可以參考下
    2017-03-03
  • JavaSE中比較器、深拷貝淺拷貝舉例詳解

    JavaSE中比較器、深拷貝淺拷貝舉例詳解

    在Java中一切都可以視為對象,在Java中我們經(jīng)常使用引用去操作對象,下面這篇文章主要給大家介紹了關(guān)于JavaSE中比較器、深拷貝淺拷貝的相關(guān)資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2024-07-07
  • ChatGPT在IDEA中使用的詳細過程

    ChatGPT在IDEA中使用的詳細過程

    這篇文章主要介紹了ChatGPT在IDEA中使用的詳細過程,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-02-02
  • Mac下設(shè)置Java默認版本的方法

    Mac下設(shè)置Java默認版本的方法

    今天工作的時候發(fā)現(xiàn)了一個錯誤,提示java版本太低,無法啟動!想起自己裝過高版本的Java,但是卻沒有默認啟動,從網(wǎng)上找了一些資料,整理下現(xiàn)在分享給大家,有需要的可以參考借鑒。
    2016-10-10

最新評論