JVM垃圾收集器與內(nèi)存分配策略

概述

說起垃圾收集器 (Garbage Collection峦阁,下文簡稱GC)辆飘,Java 內(nèi)存區(qū)域的各個(gè)部分龄捡,其中 程序計(jì)數(shù)器卓嫂、虛擬機(jī)棧、本地方法棧3個(gè)區(qū)域隨線程而生聘殖,隨線程而滅晨雳,棧中的棧幀隨著方法的進(jìn)入和退出而有條不紊執(zhí)行著出棧和入棧操作。每一個(gè)棧幀中分配多少內(nèi)存基本上是在類結(jié)構(gòu)確定下來時(shí)就已知的奸腺,因此 這幾個(gè)區(qū)域的內(nèi)存分配和回收都具備確定性餐禁,在這個(gè)區(qū)域內(nèi)就不需要過多的考慮如何回收的問題,當(dāng)方法結(jié)束或者線程結(jié)束時(shí)突照,內(nèi)存自然就跟隨著回收了帮非。


而 Java 堆和方法區(qū)這兩個(gè)區(qū)域則有不確定性:一個(gè)接口的多個(gè)實(shí)現(xiàn)類需要的內(nèi)存可能是不一樣的,一個(gè)方法所執(zhí)行的不同條件分支所需要的內(nèi)存也可能不一樣,只有處于運(yùn)行區(qū)間末盔,我們才能知道程序究竟會(huì)創(chuàng)建那些對象筑舅,創(chuàng)建多少個(gè)對象,這部分內(nèi)存的分配和回收是動(dòng)態(tài)的庄岖。垃圾收集器 所關(guān)注的正是這部分內(nèi)存該如何管理豁翎。

在這里插入圖片描述

1. 內(nèi)存分配與回收策略


對象內(nèi)存的分配,都是在堆上分配的

Java 堆是垃圾收集器管理的主要區(qū)域隅忿,因此也被稱作GC 堆(Garbage Collected Heap).從垃圾回收的角度心剥,由于現(xiàn)在收集器基本都采用分代垃圾收集算法,所以 Java 堆還可以細(xì)分為:新生代和老年代:再細(xì)致一點(diǎn)有:Eden 空間背桐、From Survivor优烧、To Survivor 空間等。進(jìn)一步劃分的目的是更好地回收內(nèi)存链峭,或者更快地分配內(nèi)存畦娄。

堆空間的基本結(jié)構(gòu):

在這里插入圖片描述

如圖所示 Eden 區(qū) 、 From Survivor0 區(qū)弊仪、To Survivor1 區(qū)都屬于新生代熙卡,Old Memory屬于老年代

1.1 對象優(yōu)先在Eden分配

大部分情況,對象都會(huì)首先在 Eden 區(qū)分配励饵,當(dāng) Eden 區(qū)沒有足夠的空間進(jìn)行分配時(shí)驳癌,虛擬機(jī)將發(fā)起一次 Minor GC, 在 一次垃圾回收后如果對象還存活,則會(huì)進(jìn)入 s0 或者 s1 役听,并且對象的年齡還會(huì)加 1 颓鲜,當(dāng)他年齡增加到一定程度(默認(rèn)為15歲),就會(huì)被存放到老年代中典予。對象存放到老年代的年齡閥值可以通過參數(shù) -XX:MaxTenuringThreshold 來設(shè)置甜滨。

案例 :

public class GCTest {

    public static void main(String[] args) {
        byte[] allocation1, allocation2,allocation3,allocation4,allocation5;
        allocation1 = new byte[32000*1024];
        allocation2 = new byte[32000*1024];
        allocation3 = new byte[1000*1024];
        allocation4 = new byte[1000*1024];
        allocation5 = new byte[1000*1024];
    }

}
在這里插入圖片描述
在這里插入圖片描述

GC輸出 :

[GC (Allocation Failure) [PSYoungGen: 37244K->928K(76288K)] 37244K->32936K(251392K), 0.0229665 secs] [Times: user=0.10 sys=0.01, real=0.02 secs] 
Heap
 PSYoungGen      total 76288K, used 36825K [0x000000076ab00000, 0x0000000774000000, 0x00000007c0000000)
  eden space 65536K, 54% used [0x000000076ab00000,0x000000076ce0e518,0x000000076eb00000)
  from space 10752K, 8% used [0x000000076eb00000,0x000000076ebe8000,0x000000076f580000)
  to   space 10752K, 0% used [0x0000000773580000,0x0000000773580000,0x0000000774000000)
 ParOldGen       total 175104K, used 32008K [0x00000006c0000000, 0x00000006cab00000, 0x000000076ab00000)
  object space 175104K, 18% used [0x00000006c0000000,0x00000006c1f42010,0x00000006cab00000)
 Metaspace       used 3138K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 345K, capacity 388K, committed 512K, reserved 1048576K

從輸出可以看出,瘤袖,虛擬機(jī)將發(fā)起一次 Minor GC 衣摩,因?yàn)?from 區(qū)已經(jīng)占用 8% ,老年代已經(jīng) 18% 當(dāng)對象無法存入 survivor 空間捂敌,所以只好通過分配擔(dān)保機(jī)制把新生代的對象提前轉(zhuǎn)移到老年代中去艾扮,老年代的空間足夠放下對象 ,所以不會(huì)出現(xiàn) Full GC黍匾。執(zhí)行玩 Minor GC 后栏渺,后面分配的對象如果能夠存在 Eden 區(qū),還是會(huì)在 Eden區(qū)分配內(nèi)存锐涯。


1.2 大對象直接進(jìn)入老年代


大對象就是指需要大量連續(xù)內(nèi)存空間的 Java 對象磕诊,最典型的大對象便是那種很長的字符串,或者元素?cái)?shù)量龐大的數(shù)組,例如上面的例子 大對象byte[] 數(shù)組 霎终,我們在寫程序的時(shí)候應(yīng)注意避免滞磺。


在 Java 虛擬機(jī)中要避免大對象的原因是,在分配空間時(shí)莱褒,他容易導(dǎo)致內(nèi)存還有不少空間時(shí)击困,就提前觸發(fā)垃圾收集,以獲取足夠的連續(xù)空間才能安置它們广凸,而當(dāng)復(fù)制對象時(shí)阅茶,大對象就意味著高額的內(nèi)存復(fù)制開銷。HotSpot虛擬機(jī)提供了 -XX:PretenureSizeThreshold 參數(shù)谅海,指定大于該設(shè)置值的對象直接在老年代分配脸哀,這樣做的目的就是避免在Eden 區(qū)及兩個(gè) Survivor 區(qū)之間來回復(fù)制,產(chǎn)生大量的復(fù)制操作扭吁。


1.3 長期存活的對象將進(jìn)入老年代


HotSpot 虛擬機(jī)中多數(shù)收集器都采用了分代手機(jī)來管理堆內(nèi)存撞蜂,那內(nèi)存回收時(shí)就必須能決策那些存活對象應(yīng)存放在新生代,你那些存活對象放在老年代中侥袜。為做到這點(diǎn)蝌诡,虛擬機(jī)給每個(gè)對象定義一個(gè)年齡 (Age)計(jì)數(shù)器,存儲(chǔ)在對象頭中枫吧,對象通常在 Eden 區(qū)誕生浦旱,如果經(jīng)過第一次 Minor GC后仍然存活,并且能被 Survivor 容納的話由蘑,該對象會(huì)被移動(dòng)到 Survivor 空間中闽寡,并且將其年齡設(shè)置為 1 歲代兵,對象在 Survivor 區(qū)沒熬過一次 Minor GC, 年齡就增加一歲尼酿,當(dāng)它的年齡增加到一定程度(默認(rèn)15歲)就會(huì)被晉升到老年代中。對象晉升老年代的年齡閾值植影,可以通過參數(shù)-XX: MaxTenuringThreshold設(shè)置裳擎。


1.4 動(dòng)態(tài)對象年齡判定


為了能更好地適應(yīng)不同程序的內(nèi)存狀況,HotSpot 虛擬機(jī)并不是永遠(yuǎn)要求對象的年齡必須達(dá)到 --XX:MaxTenuringThreshold才能晉升老年代思币,如果在 Survivor 空間中相同年齡所有對象大小的總和大于 Survivor 空間的一半鹿响,年齡大于或等于該年齡的對象就可以直接進(jìn)入老年代,無需等到 -XX: MaxTenuringThreshold中要求的年齡谷饿。

案例 :

public class GCTest {

    private static final int _1MB = 1024 * 1024;


    public static void main(String[] args) {
        testTenuringThreshold2();
    }
    /**
     * VM參數(shù):-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
     * -XX:MaxTenuringThreshold=15
     * -XX:+PrintTenuringDistribution
     */
    @SuppressWarnings("unused")
    public static void testTenuringThreshold2()
    {
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[_1MB / 4];
        // allocation1+allocation2大于survivo空間一半
        allocation2 = new byte[_1MB / 4];
        allocation3 = new byte[4 * _1MB];
        allocation4 = new byte[4 * _1MB];
        allocation4 = null;
        allocation4 = new byte[4 * _1MB]; }

}

執(zhí)行結(jié)果:

Heap
 PSYoungGen      total 9216K, used 7541K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 8192K, 92% used [0x00000007bf600000,0x00000007bfd5d738,0x00000007bfe00000)
  from space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
  to   space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
 ParOldGen       total 10240K, used 8192K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  object space 10240K, 80% used [0x00000007bec00000,0x00000007bf400020,0x00000007bf600000)
 Metaspace       used 3113K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 342K, capacity 388K, committed 512K, reserved 1048576K


1.5 進(jìn)行GC的區(qū)域


針對 Hotspot VM 的實(shí)現(xiàn)惶我,它里面的GC可以分為兩大類

部分收集(Partial GC):

  • 新生代收集(Minor GC / Young GC):只對新生代進(jìn)行垃圾收集
  • 老年代收集(Major GC / Old GC):只對老年代進(jìn)行垃圾收集
  • 混合手機(jī)(Mixed GC):對整合新生代和部分老年代進(jìn)行垃圾收集

整堆收集 :

  • Full GC : 收集整個(gè) Java 堆和方法區(qū)。


2. 如何判斷對象已死博投?


在堆里面存放著 Java 世界幾乎所有的對象實(shí)例绸贡,垃圾收集器在對堆進(jìn)行回收前,第一件事情就是要確定哪些對象還 "存活" 著,哪些對象已經(jīng) "死去"听怕。

在這里插入圖片描述

2.1 引用計(jì)數(shù)法


很多教科書判斷對象是否存活的算法是這樣的 : 在對象中添加一個(gè)引用計(jì)數(shù)器捧挺,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器值就加一尿瞭;當(dāng)引用失效時(shí)闽烙,計(jì)數(shù)器值就減一;任何時(shí)刻計(jì)數(shù)器為零的對象就是不可能被使用的声搁。

客觀的說黑竞,引用計(jì)數(shù)算法(Reference Counting)雖然占用了一些額外的內(nèi)存空間來進(jìn)行計(jì)數(shù),但 它的原理簡單疏旨,判定效率也很高摊溶,在大多數(shù)情況下它都是一個(gè)不錯(cuò)的算法。但是充石,在Java 領(lǐng)域莫换,至少主流的 Java 虛擬機(jī)里面都沒有選用引用計(jì)數(shù)算法進(jìn)行管理內(nèi)存,主要原因是骤铃,這個(gè)看似簡單的算法有很多例外的情況要考慮拉岁,必須配合大量額外處理才能保證正確的工作,譬如單純的引用計(jì)數(shù) 就很難解決對象之間相互循環(huán)引用的問題惰爬。


/**
 * testGC()方法執(zhí)行后喊暖,objA和objB會(huì)不會(huì)被GC呢?
 */
public class ReferenceCountingGc {
    Object instance = null;
    public static void main(String[] args) {
        ReferenceCountingGc objA = new ReferenceCountingGc();
        ReferenceCountingGc objB = new ReferenceCountingGc();
        objA.instance = objB;
        objB.instance = objA;
        objA = null;
        objB = null;
        // 假設(shè)在這行發(fā)生GC撕瞧,objA和objB是否能被回收陵叽?
         System.gc();

    }
}


2.2 可達(dá)性分析


這個(gè)算法的基本思想就是通過一系列的稱為 “GC Roots” 的對象作為起點(diǎn),從這些接待您開始向下搜索丛版,節(jié)點(diǎn)所走過的路徑稱為引用鏈巩掺,當(dāng)一個(gè)對象到 GC Roots 沒有任何引用鏈相連的話,則證明此對象是不可用的页畦。

在這里插入圖片描述

可作為 GC Roots 的對象包括:

  • 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象
  • 本地方法棧(Native 方法)中引用對象
  • 方法區(qū)中類靜態(tài)屬性引用對象
  • 方法區(qū)中常量引用的對象
  • 所有被同步鎖持有的對象


2.3 引用


無論是通過引用計(jì)數(shù)算法判斷對象的引用數(shù)量胖替,還是通過可達(dá)性分析算法判斷對象是否引用鏈可達(dá),判斷對象是否存活都和 “引用” 離不開關(guān)系 豫缨。

在 JDK1.2 之前独令,Java 里的引用還是很傳統(tǒng)的定義:如果 reference 類型的數(shù)據(jù)中存儲(chǔ)的數(shù)值代表的是另外一塊內(nèi)存起始地址,就稱該reference數(shù)據(jù)是代表 某塊內(nèi)存好芭、某個(gè)對象的引用燃箭。這種定義并沒有什么不對,只是現(xiàn)在看來有些過于狹隘了舍败,一個(gè)對象在這種定義下只有 "被引用" 或者 "未被引用" 兩種狀態(tài)招狸,對于描述一些 "食之無味碗硬,棄之可惜" 的對象就顯的無能為力了,譬如我們希望能描述一類對象:當(dāng)內(nèi)存空間還足夠時(shí)瓢颅,能保留在內(nèi)存之中恩尾,如果內(nèi)存空 間在進(jìn)行垃圾收集后仍然非常緊張,那就可以拋棄這些對象——很多系統(tǒng)的緩存功能都符合這樣的應(yīng) 用場景挽懦。

在 JDK1.2 之后翰意,Java 對引用的概念進(jìn)行了擴(kuò)充,將引用分為強(qiáng)引用(Strongly Re-ference)信柿、軟 引用(Soft Reference)冀偶、弱引用(Weak Reference)和虛引用(Phantom Reference)4種,這4種引用強(qiáng) 度依次逐漸減弱渔嚷。

  • 強(qiáng)引用

    強(qiáng)引用是最傳統(tǒng)的 "引用" 的定義 进鸠,是指在程序代碼之中普遍存在的引用賦值,即類似“Object obj=new Object()”這種引用關(guān)系形病。無論任何情況下客年,只要強(qiáng)引用關(guān)系還存在,垃圾收集器就永遠(yuǎn)不會(huì)回 收掉被引用的對象漠吻。

  • 軟引用

    軟引用是用來描述一些還有用量瓜,但非必要的對象。只被軟引用關(guān)聯(lián)著的對象途乃,在系統(tǒng)將要發(fā)生內(nèi)存溢出錢绍傲,會(huì)把這些對象列進(jìn)回收范圍之中進(jìn)行二次回收,如果這次回收還沒有足夠的內(nèi)存耍共,才會(huì)拋出內(nèi)存溢出異常烫饼。在 JDK 1.2 之后提供了 SoftReference 類實(shí)現(xiàn)軟引用。

  • 弱引用

    弱引用也是用來描述哪些非必需對象试读,但是它的強(qiáng)度比軟引用更弱一些杠纵,被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾收集發(fā)生為止。當(dāng)垃圾收集器開始工作鹏往,無論當(dāng)前內(nèi)存是否足夠淡诗,都會(huì)回收之被弱引用的關(guān)聯(lián)的對象骇塘。在JDK 1.2版之后提供了WeakReference類來實(shí)現(xiàn)弱引用伊履。

  • 虛引用

    虛引用也稱為 "幽靈引用" 或者 "幻影引用" ,它是最弱的一種引用關(guān)系款违。一個(gè)對象是否有需引用的存在唐瀑,完全不會(huì)對其生存時(shí)間構(gòu)成影響,也無法通過虛引用來去的一個(gè)對象實(shí)例插爹。為一個(gè)對象設(shè)置虛引用關(guān)聯(lián)的唯一目的只是為了能在這個(gè)對象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知哄辣。在JDK 1.2版之后提供 了PhantomReference類來實(shí)現(xiàn)虛引用请梢。


2.4 對象生存還是死亡


即使在可達(dá)性分析法中不可達(dá)達(dá)對象,也并非是 "非死不可" 的力穗,這個(gè)時(shí)候它們處于 "緩刑階段"毅弧,要真正宣告一個(gè)對象死亡,至少要經(jīng)歷兩次經(jīng)歷過程当窗;可達(dá)性分析法中不可達(dá)的對象唄第一次標(biāo)記并且進(jìn)行篩選够坐,篩選的條件是此對象是否有必要執(zhí)行 finalize 方法。當(dāng)對象沒有覆蓋 finalize 方法崖面,或 finalize 方法已經(jīng)被虛擬機(jī)調(diào)用過時(shí)元咙,虛擬機(jī)將這兩種情況視為沒有必要執(zhí)行。

被判定為需要執(zhí)行的對象將會(huì)被放在一個(gè)隊(duì)列中進(jìn)行第二次標(biāo)記巫员,除非這個(gè)對象與引用鏈上的任何一個(gè)對象建立關(guān)聯(lián)庶香,否則就會(huì)被真的回收。


2.5 回收方法區(qū)


方法區(qū)的垃圾收集主要分為兩部分內(nèi)容:廢棄的常量和不再使用的類型简识「弦矗回收廢棄常量與回收 Java堆中的對象非常類似。

  • 廢棄的常量

廢棄的常量 :假如一個(gè)字符串 "Java" 曾經(jīng)進(jìn)入常量池 中七扰,但是當(dāng)前系統(tǒng)又沒有任何一個(gè)字符串對象的值是 "Java"倘零,換句話,已經(jīng)沒有任何字符串對象引用常量池的 "Java" 戳寸,且虛擬機(jī)中也沒有其他地方引用這個(gè)字面量呈驶。如果在這時(shí)發(fā)生內(nèi)存回收,而且垃圾收集器判斷確有必要的話疫鹊,這個(gè) "Java" 常量就會(huì)被系統(tǒng)清理出常量池袖瞻。常量池其他類 (接口) 、方法拆吆、字段的符號引用也與此類似聋迎。

  • 不在使用的類型

判定一個(gè)常量是否“廢棄”還是相對簡單,而要判定一個(gè)類型是否屬于“不再被使用的類”的條件就 比較苛刻了枣耀。需要同時(shí)滿足下面三個(gè)條件:

  1. 該類所有的實(shí)例都已經(jīng)被回收霉晕,也就是Java堆中不存在該類及其任何派生子類的實(shí)例。
  2. 加載該類的類加載器已經(jīng)被回收捞奕,這個(gè)條件除非是經(jīng)過精心設(shè)計(jì)的可替換類加載器的場景牺堰,如 OSGi、JSP的重加載等颅围,否則通常是很難達(dá)成的伟葫。
  3. 該類對應(yīng)的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方 法院促。


虛擬機(jī)可以對滿足上述 3 個(gè)條件的無用類進(jìn)行回收筏养,這里說的僅僅是“可以”斧抱,而并不是和對象一樣不使用了就會(huì)必然被回收。


3. 垃圾收集算法


在這里插入圖片描述


3.1 分代收集算法


當(dāng)前商業(yè)虛擬機(jī)的垃圾收集器渐溶,大多數(shù)都遵循了 "分代收集" (Generational Collection)都理論進(jìn)行設(shè)計(jì)的辉浦,分代收集名為理論,實(shí)質(zhì)是一套符合大多數(shù)程序運(yùn)行實(shí)際情況的經(jīng)驗(yàn)法則茎辐,它建立在兩個(gè)分代假說之上

  1. 弱分代假說(Weak Generational Hypothesis):絕大多數(shù)對象都是朝生夕滅的盏浙。
  2. 強(qiáng)分代假說(Strong Generational Hypothesis):熬過越多次垃圾收集過程的對象就越難以消 亡。

這兩個(gè)分代假說共同奠定了多款常用的垃圾收集器的一致設(shè)計(jì)原則 : 收集器應(yīng)該將 Java 堆劃分出不同的區(qū)域荔茬,然后將回收對象依據(jù)其年齡(年齡即對象熬過垃圾收集過程的次數(shù))分配到不同的區(qū)域存儲(chǔ)废膘。顯而易見,如果一個(gè)區(qū)域中大多數(shù)對象都是朝生夕滅慕蔚,難以熬過垃圾回收的話丐黄,那么把它們集中放在一起,每次回收時(shí)只關(guān)注如何保留少量存活而不是標(biāo)記哪些大量將要被回收的對象孔飒,就能以較低代價(jià)回收到大量的空間如果剩下的都是難以消亡的對象灌闺,那把它們集中放在一塊, 虛擬機(jī)便可以使用較低的頻率來回收這個(gè)區(qū)域坏瞄,這就同時(shí)兼顧了垃圾收集的時(shí)間開銷和內(nèi)存的空間有 效利用桂对。

設(shè)計(jì)者一般至少會(huì)把Java堆劃分為新生代 (Young Generation)和老年代(Old Generation)兩個(gè)區(qū)域[2]。顧名思義鸠匀,在新生代中蕉斜,每次垃圾收集 時(shí)都發(fā)現(xiàn)有大批對象死去,而每次回收后存活的少量對象缀棍,將會(huì)逐步晉升到老年代中存放


3.2 標(biāo)記清除算法


最早出現(xiàn)也是最基礎(chǔ)的垃圾算法是 "標(biāo)記-清除" (Mark-Sweep)算法宅此,如圖它的名字一樣,算法分為 "標(biāo)記" 和 "清除" 兩個(gè)階段 :首先標(biāo)記出所有需要回收的對象爬范,在標(biāo)記完成后父腕,統(tǒng)一回收掉所有被標(biāo)記的對象,也可以返過來青瀑,標(biāo)記存活的對象璧亮,統(tǒng)一回收所有未標(biāo)記的對象。標(biāo)記過程就是對象是否屬于垃圾的判定過程

這種垃圾算法會(huì)帶來2個(gè)明顯的問題

  • 效率問題
  • 空間問題 (標(biāo)記清除后產(chǎn)生大量不連續(xù)的碎片)
在這里插入圖片描述


3.3 標(biāo)記-復(fù)制算法


標(biāo)記-復(fù)制算法常被簡稱為復(fù)制算法斥难,為了解決標(biāo)記-清除算法面對大量可回收對象時(shí)執(zhí)行效率低的問題枝嘶,"標(biāo)記-復(fù)制" 收集算法出現(xiàn)了。它可以將內(nèi)存分為大小相同的兩塊蘸炸,每次使用其中一塊躬络。當(dāng)這一塊的內(nèi)存使用完后,就將還存活的對象復(fù)制到另一塊去搭儒,然后再把使用的空間一次清理掉穷当。這樣就使每次的內(nèi)存回收都是對內(nèi)存區(qū)間的一半進(jìn)行回收。

在這里插入圖片描述


3.4 標(biāo)記-整理算法


標(biāo)記-復(fù)制算法在對象存活率較高時(shí)就要進(jìn)行較多的復(fù)制操作淹禾,效率將會(huì)降低馁菜。更關(guān)鍵的是,如果 不想浪費(fèi)50%的空間铃岔,就需要有額外的空間進(jìn)行分配擔(dān)保汪疮,以應(yīng)對被使用的內(nèi)存中所有對象都100%存 活的極端情況,所以在老年代一般不能直接選用這種算法毁习。

針對老年代對象的存亡特征智嚷,提出了另外一種有針對性的“標(biāo)記-整 理”(Mark-Compact)算法,其中的標(biāo)記過程仍然與“標(biāo)記-清除”算法一樣纺且,但后續(xù)步驟不是直接對可 回收對象進(jìn)行清理盏道,而是讓所有存活的對象都向內(nèi)存空間一端移動(dòng),然后直接清理掉邊界以外的內(nèi) 存载碌。

在這里插入圖片描述


4. 垃圾收集器


在這里插入圖片描述

如果說收集算法時(shí)內(nèi)存回收的方法論猜嘱,那么垃圾收集器就是內(nèi)存回收實(shí)踐者。

雖然我們會(huì)對各個(gè)收集器進(jìn)行比 較嫁艇,但并非為了挑選一個(gè)最好的收集器出來,雖然垃圾收集器的技術(shù)在不斷進(jìn)步,但直到現(xiàn)在還沒有 最好的收集器出現(xiàn)瞒瘸,更加不存在“萬能”的收集器拳锚,所以我們選擇的只是對具體應(yīng)用最合適的收集器。


4.1 Serial收集器


Serial 收集器是最基礎(chǔ)猾漫、歷史最悠久的收集器纯丸,在 JDK1.3之前 是HotSpot 虛擬機(jī)新生代收集器唯一選擇。這個(gè)收集器是一個(gè)單線程工作的收集器静袖,但它的“單線 程”的意義并不僅僅是說明它只會(huì)使用一個(gè)處理器或一條收集線程去完成垃圾收集工作觉鼻,更重要的是強(qiáng) 調(diào)在它進(jìn)行垃圾收集時(shí),必須暫停其他所有工作線程队橙,直到它收集結(jié)束坠陈。

在這里插入圖片描述

虛擬機(jī)的設(shè)計(jì)者們當(dāng)然知道 Stop The World 帶來的不良用戶體驗(yàn),所以在后續(xù)的垃圾收集器設(shè)計(jì)中停頓時(shí)間在不斷縮短(仍然還有停頓捐康,尋找最優(yōu)秀的垃圾收集器的過程仍然在繼續(xù))仇矾。

但是 Serial 收集器有沒有優(yōu)于其他垃圾收集器的地方呢?當(dāng)然有解总,它簡單而高效(與其他收集器的單線程相比)贮匕。Serial 收集器由于沒有線程交互的開銷,自然可以獲得很高的單線程收集效率花枫。Serial 收集器對于運(yùn)行在 Client 模式下的虛擬機(jī)來說是個(gè)不錯(cuò)的選擇刻盐。


4.2 ParNew收集器


ParNew收集器實(shí)質(zhì)上是 Serial收集器的多線程并行版本掏膏,除了同時(shí)使用多條線程進(jìn)行垃圾收集之外其余的行為包括Serial收集器可用的所有控制參數(shù)(例如:-XX:SurvivorRatio、-XX: PretenureSizeThreshold敦锌、-XX:HandlePromotionFailure等)馒疹、收集算法、Stop The World乙墙、對象分配規(guī) 則颖变、回收策略等都與Serial收集器完全一致

在這里插入圖片描述

ParNew收集器除了支持多線程并行收集之外,尤其是JDK 7之前的遺留系統(tǒng)中首選的新生代收集 器听想,其中有一個(gè)與功能腥刹、性能無關(guān)但其實(shí)很重要的原因是:除了Serial收集器外,目前只有它能與CMS 收集器配合工作汉买。


4.3 Parallel Scavenge收集器


Parallel Scavenge收集器也是一款新生代收集器, 它同樣是基于標(biāo)記-復(fù)制算法實(shí)現(xiàn)的收集器衔峰,也是能夠并行收集的多線程收集器

  • Parallel Scavenge 和 ParNew 特別之處
-XX:+UseParallelGC

    使用 Parallel 收集器+ 老年代串行

-XX:+UseParallelOldGC

    使用 Parallel 收集器+ 老年代并行

由于與吞吐量關(guān)系密切,Parallel Scavenge收集器也經(jīng)常被稱作“吞吐量優(yōu)先收集器”录别。Parallel Scavenge 收集器關(guān)注點(diǎn)是吞吐量(高效率的利用 CPU)朽色。CMS等垃圾收集器關(guān)注點(diǎn)更多的是用戶線程停頓時(shí)間(提高用戶體驗(yàn))。所謂吞吐量就是 CPU 用于運(yùn)行用戶代碼的時(shí)間與 CPU 總小號時(shí)間的比值组题。

Parallel Scavenge 收集器提供了很多參數(shù)供用戶找到最合適的停頓時(shí)間或最大吞吐量葫男,如果對于收集器運(yùn)作不太了解,手工優(yōu)化存在困難的時(shí)候崔列,使用 Parallel Scavenge 收集器配合自適應(yīng)調(diào)節(jié)策略梢褐,把內(nèi)存管理優(yōu)化交給虛擬機(jī)去完成也是一個(gè)不錯(cuò)的選擇。這種重中之重重中之重


這是 JDK1.8 默認(rèn)收集器

使用 java -XX:+PrintCommandLineFlags -version 命令查看

-XX:InitialHeapSize=262921408 -XX:MaxHeapSize=4206742528 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
java version "1.8.0_211"
Java(TM) SE Runtime Environment (build 1.8.0_211-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.211-b12, mixed mode)

JDK1.8 默認(rèn)使用的是 Parallel Scavenge + Parallel Old赵讯,如果指定了-XX:+UseParallelGC 參數(shù)盈咳,則默認(rèn)指定了-XX:+UseParallelOldGC,可以使用-XX:-UseParallelOldGC 來禁用該功能


4.4 Seria Old 收集器


Serial Old是Serial收集器的老年代版本边翼,它同樣是一個(gè)單線程收集器鱼响。它主要有兩大用途:一種用途是在 JDK1.5 以及以前的版本中與 Parallel Scavenge 收集器搭配使用,另一種用途是作為 CMS 收集器的后備方案组底。


4.5 Parallel Old收集器


Parallel Old是Parallel Scavenge收集器的老年代版本丈积,支持多線程并發(fā)收集,基于標(biāo)記-整理算法實(shí)現(xiàn)债鸡。在注重吞吐量以及 CPU 資源的場合江滨,都可以優(yōu)先考慮 Parallel Scavenge 收集器和 Parallel Old 收集器。


4.6 CMS收集器


CMS (Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器厌均。目前很大一部分的 Java 應(yīng)用集在互聯(lián)網(wǎng)網(wǎng)站或者基于瀏覽器的 B/S 系統(tǒng)服務(wù)器上唬滑,這類應(yīng)用通常都會(huì)較為 關(guān)注服務(wù)的響應(yīng)速度,希望系統(tǒng)停頓時(shí)間盡可能短,以給用戶帶來良好的交互體驗(yàn)晶密。CMS收集器就非 常符合這類應(yīng)用的需求擒悬。

從名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于標(biāo)記-清除算法實(shí)現(xiàn)的,它的運(yùn)作 過程相對于前面幾種收集器來說要更復(fù)雜一些惹挟,整個(gè)過程分為四個(gè)步驟茄螃,包括:

  1. 初始標(biāo)記(CMS initial mark):暫停所有的其他線程缝驳,并記錄下直接與 root 相連的對象连锯,速度很快
  2. 并發(fā)標(biāo)記(CMS concurrent mark):從GC roots的直接關(guān)聯(lián)對象開始遍歷整個(gè)對象圖的過程, 這個(gè)過程耗時(shí)較長但是不需要停頓用戶線程,可以與卡機(jī)手機(jī)線程一起并發(fā)運(yùn)行
  3. 重新標(biāo)記(CMS remark):為了修正并發(fā)標(biāo)記期間用狱,因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對象的標(biāo)記記錄运怖,這個(gè)階段的停頓時(shí)間通常會(huì)比初始標(biāo)記階段稍長一 些,但也遠(yuǎn)比并發(fā)標(biāo)記階段的時(shí)間短
  4. 并發(fā)清除(CMS concurrent sweep):強(qiáng)力刪除掉標(biāo)記階段判斷的已經(jīng)死亡的對象夏伊,由于不需要移動(dòng)存活對象摇展,所以整個(gè)階段也是可以與用戶線程同時(shí)并發(fā)的。
在這里插入圖片描述

從它的名字就可以看出它是一款優(yōu)秀的垃圾收集器溺忧,主要優(yōu)點(diǎn):并發(fā)收集咏连、低停頓。但是它有下面三個(gè)明顯的缺點(diǎn):

  • 對 CPU 資源敏感
  • 無法處理浮動(dòng)垃圾
  • 它使用的回收算法-“標(biāo)記-清除”算法會(huì)導(dǎo)致收集結(jié)束時(shí)會(huì)有大量空間碎片產(chǎn)生鲁森。


4.6 Garbage First收集器


Garbage First(簡稱G1)收集器是垃圾收集器技術(shù)發(fā)展歷史上的里程碑式的成果祟滴,它開創(chuàng)了收集 器面向局部收集的設(shè)計(jì)思路和基于Region的內(nèi)存布局形式。

G1 (Garbage-First) 是一款面向服務(wù)器的垃圾收集器,主要針對配備多顆處理器及大容量內(nèi)存的機(jī)器. 以極高概率滿足 GC 停頓時(shí)間要求的同時(shí),還具備高吞吐量性能特征

  • 并發(fā)與并行 : G1 能充分利用 CPU歌溉、多核環(huán)境下的硬件優(yōu)勢垄懂,使用多個(gè) CPU(CPU 或者 CPU 核心)來縮短 Stop-The-World 停頓時(shí)間。部分其他收集器原本需要停頓 Java 線程執(zhí)行的 GC 動(dòng)作痛垛,G1 收集器仍然可以通過并發(fā)的方式讓 java 程序繼續(xù)執(zhí)行草慧。
  • 分代收集 : 雖然 G1 可以不需要其他收集器配合就能獨(dú)立管理整個(gè) GC 堆,但是還是保留了分代的概念匙头。
  • 空間整合 : 與 CMS 的“標(biāo)記-清理”算法不同漫谷,G1 從整體來看是基于“標(biāo)記-整理”算法實(shí)現(xiàn)的收集器;從局部上來看是基于“標(biāo)記-復(fù)制”算法實(shí)現(xiàn)的蹂析。
  • 可預(yù)測的停頓 : 這是 G1 相對于 CMS 的另一個(gè)大優(yōu)勢舔示,降低停頓時(shí)間是 G1 和 CMS 共同的關(guān)注點(diǎn),但 G1 除了追求低停頓外识窿,還能建立可預(yù)測的停頓時(shí)間模型斩郎,能讓使用者明確指定在一個(gè)長度為 M 毫秒的時(shí)間片段內(nèi)。

G1 收集器的運(yùn)作大致分為以下幾個(gè)步驟:

  • 初始標(biāo)記
  • 并發(fā)標(biāo)記
  • 最終標(biāo)記
  • 篩選回收

G1 收集器在后臺(tái)維護(hù)了一個(gè)優(yōu)先列表喻频,每次根據(jù)允許的收集時(shí)間缩宜,優(yōu)先選擇回收價(jià)值最大的 Region(這也就是它的名字 Garbage-First 的由來) 。這種使用 Region 劃分內(nèi)存空間以及有優(yōu)先級的區(qū)域回收方式,保證了 G1 收集器在有限時(shí)間內(nèi)可以盡可能高的收集效率(把內(nèi)存化整為零)锻煌。


4.6 ZGC收集器


ZGC(Z Garbage Collector)是一款在 JDK 11中新加入的具有實(shí)驗(yàn)性質(zhì)[1]的低延遲垃圾收集器妓布,是由Oracle公司研發(fā)的。2018年Oracle創(chuàng)建了 JEP 333將ZGC提交給OpenJDK宋梧,推動(dòng)其進(jìn)入OpenJDK 11的發(fā)布清單之中匣沼。

與 CMS 中的 ParNew 和 G1 類似,ZGC 也采用標(biāo)記-復(fù)制算法捂龄,不過 ZGC 對該算法做了重大改進(jìn)释涛。

ZGC運(yùn)作過程:

  • 并發(fā)標(biāo)記
  • 并發(fā)預(yù)備重分配
  • 并發(fā)重分配
  • 并發(fā)重映射


個(gè)人博客地址:http://blog.yanxiaolong.cn | <font color = "orange"> 『縱有疾風(fēng)起,人生不言棄』 </font>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末倦沧,一起剝皮案震驚了整個(gè)濱河市唇撬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌展融,老刑警劉巖窖认,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異告希,居然都是意外死亡扑浸,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進(jìn)店門燕偶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來喝噪,“玉大人,你說我怎么就攤上這事杭跪∠陕撸” “怎么了?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵涧尿,是天一觀的道長系奉。 經(jīng)常有香客問我,道長姑廉,這世上最難降的妖魔是什么缺亮? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮桥言,結(jié)果婚禮上萌踱,老公的妹妹穿的比我還像新娘。我一直安慰自己号阿,他們只是感情好并鸵,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著扔涧,像睡著了一般园担。 火紅的嫁衣襯著肌膚如雪届谈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天弯汰,我揣著相機(jī)與錄音艰山,去河邊找鬼。 笑死咏闪,一個(gè)胖子當(dāng)著我的面吹牛曙搬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鸽嫂,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼纵装,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了溪胶?” 一聲冷哼從身側(cè)響起搂擦,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤稳诚,失蹤者是張志新(化名)和其女友劉穎哗脖,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扳还,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡才避,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了氨距。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桑逝。...
    茶點(diǎn)故事閱讀 40,001評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖俏让,靈堂內(nèi)的尸體忽然破棺而出楞遏,到底是詐尸還是另有隱情,我是刑警寧澤首昔,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布寡喝,位于F島的核電站,受9級特大地震影響勒奇,放射性物質(zhì)發(fā)生泄漏预鬓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一赊颠、第九天 我趴在偏房一處隱蔽的房頂上張望格二。 院中可真熱鬧,春花似錦竣蹦、人聲如沸顶猜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽长窄。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抄淑,已是汗流浹背屠凶。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肆资,地道東北人矗愧。 一個(gè)月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像郑原,于是被迫代替她去往敵國和親唉韭。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評論 2 355