<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    JVM垃圾回收

    共 9261字,需瀏覽 19分鐘

     ·

    2020-12-25 13:22


    看到垃圾回收,首先你會(huì)想到什么?

    1、什么是垃圾?

    2、哪些地方的垃圾需要被回收?

    3、如何定位垃圾?

    4、如何回收垃圾?

    5、什么時(shí)候回收垃圾?

    下面,我們將帶著這5個(gè)問題來進(jìn)行分析。


    1、什么是垃圾?

    JVM中的垃圾指的是無用的內(nèi)存,這些內(nèi)存中的數(shù)據(jù)若在后續(xù)處理過程中不再被使用,那么我們將是為垃圾進(jìn)行回收,以保證有足夠的內(nèi)存可用。

    程序在運(yùn)行過程中會(huì)存在兩個(gè)問題:

    • 內(nèi)存溢出:是指內(nèi)存空間分配不足

    • 內(nèi)存泄露:是指內(nèi)存使用完后無法被回收


    2、哪些地方的垃圾需要被回收?

    • 線程私有內(nèi)存區(qū)域(程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧)會(huì)隨著線程的結(jié)束而釋放,棧中的棧幀隨著方法的進(jìn)入和退出有條不紊的執(zhí)行者進(jìn)棧和出棧操作,每個(gè)棧幀中的分配多少內(nèi)存在編譯期便已確定,因此無需對(duì)該區(qū)域進(jìn)行垃圾回收。

    • 線程共享內(nèi)存區(qū)域(方法區(qū)和Java堆)需要分配的內(nèi)存大小只有在運(yùn)行期才知道,因此這部分的內(nèi)存是動(dòng)態(tài)分配和回收的,也是我們接下來需要對(duì)垃圾內(nèi)存回收的區(qū)域。


    3、如何定位垃圾?

    因?yàn)槲覀兝厥盏闹饕獏^(qū)域是針對(duì)于Java堆(又稱為GC堆),這部分區(qū)域主要存放的是Java對(duì)象的實(shí)例,因此判斷內(nèi)存是否可以被回收只需要確認(rèn)實(shí)例對(duì)象已經(jīng)不被使用,即對(duì)象是否存活。

    判斷對(duì)象是否的方法有兩種,一種是引用計(jì)數(shù)算法,另一種是可達(dá)性分析算法。下面我們來介紹一下這兩中算法的優(yōu)缺點(diǎn)。

    1)引用計(jì)數(shù)算法

    首先我們來說一下這個(gè)算法的實(shí)現(xiàn)思想:為每個(gè)對(duì)象添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器的值加1,當(dāng)引用失效時(shí),計(jì)數(shù)器的值減1。當(dāng)計(jì)數(shù)器的值為0時(shí)表示該對(duì)象沒有被引用。

    上述實(shí)現(xiàn)思想看起了一點(diǎn)毛病都沒有,實(shí)現(xiàn)起來也是非常的簡單,而且效率也比較高,但是既然會(huì)有其他的算法被使用,那么該算法也就一定有他的缺點(diǎn)。

    • 優(yōu)點(diǎn):實(shí)現(xiàn)簡單且效率高

    • 缺點(diǎn):無法處理對(duì)象互相循環(huán)引用的情況。

    對(duì)象的互相循環(huán)引用?聽起來不太容易理解,那我們就來舉個(gè)例子吧

    public class ReferenceCountingGC {
    public Object instance = null;
    public static void testGC () {
    ReferenceCountingGC objA = new ReferenceCountingGC();
    ReferenceCountingGC objB = new ReferenceCountingGC();

    objA.instance = objB;
    objB.instance = objA;

    objA = null;
    objB = null;

    System.gc();

    }
    }

    上述代碼中,對(duì)象A和對(duì)象B彼此引用,雖然對(duì)其句柄賦值為null,但是兩個(gè)對(duì)象的引用計(jì)數(shù)器的值均為1,因此使用引用計(jì)數(shù)算法無法對(duì)上述兩個(gè)對(duì)象進(jìn)行回收。

    2)可達(dá)性分析算法

    目前主流的程序語言的實(shí)現(xiàn)中都稱是通過可達(dá)性分析算法來判斷對(duì)象是否存活。其算法的基本思想是:通過一系列被稱為”GC Roots“的對(duì)象作為起始點(diǎn),開始向下搜索,搜索所走過的路徑成為引用鏈,當(dāng)一個(gè)對(duì)象到GC Roots沒有任何的引用鏈時(shí),則證明此對(duì)象是不可用的。


    bb41164d6d0335c1b1f96c3915c3239d.webp



    上面提到了一個(gè)“GC Roots”,那么哪些對(duì)方的對(duì)象是可以作為GC Roots呢?

    在Java語言中,可作為GC Roots的對(duì)象包括:

    a. 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象

    b. 本地方法棧中JNI(native方法)引用的對(duì)象

    c. 方法區(qū)中類靜態(tài)屬性引用的對(duì)象

    d. 方法區(qū)中常量引用的對(duì)象


    4、如何回收垃圾?

    關(guān)于如何回收垃圾這一部分我們要分為兩部分進(jìn)行講解:垃圾收集算法垃圾收集器

    第一部分 垃圾收集算法

    垃圾收集算法主要包括三種算法:標(biāo)記-清除算法復(fù)制算法、標(biāo)記-整理算法

    另外還有分代收集算法,這種算法沒有特別的思想,而是根據(jù)對(duì)象存活周期的不同將內(nèi)存劃分為幾塊,然后根據(jù)對(duì)應(yīng)內(nèi)存區(qū)域的特點(diǎn)采用適當(dāng)?shù)氖占惴ā?/p>

    下面我們分別對(duì)上述三種算法進(jìn)行分析說明。

    1)標(biāo)記-清除算法

    實(shí)現(xiàn)思路:分為兩個(gè)階段,第一個(gè)階段是標(biāo)記,如何標(biāo)記(定位)垃圾已經(jīng)在第3點(diǎn)中介紹過了。第二個(gè)階段是直接清除。

    標(biāo)記-清除算法的執(zhí)行過程如圖所示。它存在兩個(gè)不足之處:

    a. 效率問題,標(biāo)記和清除兩個(gè)過程的效率都不高。

    b. 空間問題,標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片,可能會(huì)導(dǎo)致在后續(xù)分配較大對(duì)象是沒有足夠的空間導(dǎo)致出發(fā)一次內(nèi)存收集的動(dòng)作。


    ff80f5c3eeb980547f287f17266ce678.webp2423a759b670dd9277e5108718af5496.webp


    2)復(fù)制算法

    實(shí)現(xiàn)思路:將內(nèi)存按照大小劃分為兩塊,每次只使用其中的一塊,當(dāng)另一塊的內(nèi)存用完了,就將還存活的對(duì)象復(fù)制到另一塊的上面,然后再把當(dāng)前內(nèi)存區(qū)域一次清理掉。

    優(yōu)點(diǎn):內(nèi)存分配時(shí)只需要按照順序分配即可,實(shí)現(xiàn)簡單,運(yùn)行效率高。

    缺點(diǎn):犧牲了一半的內(nèi)存空間

    目前商業(yè)虛擬機(jī)并非將內(nèi)存空間按照1:1的比例來劃分內(nèi)存,而是將內(nèi)存分為一塊較大空間的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor?;厥諘r(shí)將Eden和Survivor中還存活的對(duì)象一次性復(fù)制到另一塊Survivor空間。

    HotSpot虛擬機(jī)默認(rèn)Eden和Survivor的大小比例是8:1,每次新生代中可用的內(nèi)存空間為整個(gè)新生代容量的90%。這里面臨著兩個(gè)問題:一是什么場景適合使用該方式,二是如果Eden和Survivor存活的對(duì)象超過10%該怎么處理?

    • 適合使用的場景:在絕大數(shù)情況下,Eden和Survivor在回收后存活的對(duì)象小于10%

    • 超過10%的處理方式:內(nèi)存分配擔(dān)保。我們將內(nèi)存空間分為新生代(Eden和Survivor)和老年代,如果Eden和Survivor回收后的存活對(duì)象超過10%,則直接進(jìn)入老年代。


    ad24ba75be88f37dc61121e2ea905057.webp5c90ca99e187d0e80ec6acaabba95918.webp


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

    實(shí)現(xiàn)思路:類似于標(biāo)記-清除算法,不同的是不直接對(duì)可回收的對(duì)象進(jìn)行清理,而是讓所有存活的對(duì)象都想前一端移動(dòng),然后清理掉端邊界以外的內(nèi)存。

    優(yōu)點(diǎn):不會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存空間,適用于老年代


    fcb7e225656b48ad567b37fdc94f8e4a.webp001330e70e3a65dfe5c169da7d4a7418.webp


    4)分代收集算法

    將Java堆劃分為新生代和老年代。

    新生代在每次垃圾收集時(shí)會(huì)有大批對(duì)象死去,適合采用復(fù)制算法。

    老年代對(duì)象存活率高且沒有額外空間進(jìn)行分配擔(dān)保,因此適合采用標(biāo)記-整理算法。


    第二部分 垃圾收集器

    接下來,我們將對(duì)HotSpot虛擬機(jī)中不同jdk版本中使用過的垃圾收集器的實(shí)現(xiàn)進(jìn)行分析,下圖對(duì)可以組合使用的垃圾收集器通過聯(lián)系進(jìn)行了關(guān)聯(lián)。

    新生代的垃圾收集器

    • Serial

    • ParNew

    • Parallel Scavenge

    老年代的垃圾收集器

    • Serial Old

    • CMS(Concurrent Mark Sweep)

    • Parallel Old

    新生代和老年代均可用G1垃圾收集器

    以及在JDK11中的ZGC垃圾收集器

    關(guān)于垃圾收集過程的個(gè)人理解:

    無論是何種收集算法何種收集器,我們?cè)跇?biāo)記、清除、復(fù)制、整理這幾個(gè)過程需要保證一點(diǎn),不能清理掉正在被引用的對(duì)象。在這個(gè)基礎(chǔ)之上,我們接下來要做的就是兩點(diǎn),一是提升收集效率,二是提升用戶體驗(yàn)。

    提到對(duì)象的標(biāo)記,我產(chǎn)生了一個(gè)疑問:難道當(dāng)我們需要進(jìn)行垃圾標(biāo)記時(shí)真的要(從虛擬機(jī)棧棧幀中的本地變量表中引用的對(duì)象、方法區(qū)中類的靜態(tài)屬性引用的對(duì)象、方法區(qū)中常量引用的對(duì)象、本地方法棧中引用的對(duì)象)一個(gè)一個(gè)的去遍歷嗎?想想都覺得這個(gè)過程真的好耗時(shí)啊.....這一點(diǎn)當(dāng)前主流的虛擬機(jī)已經(jīng)考慮到了,在 HotSpot 虛擬機(jī)中使用了 OopMap 的數(shù)組結(jié)構(gòu)來幫我們存放了對(duì)象引用。

    1. 在類加載時(shí),HotSpot 會(huì)將對(duì)象內(nèi)什么偏移量上是什么類型的數(shù)據(jù)計(jì)算出來

    2. 在JIT編譯時(shí),會(huì)在特定未知記錄下棧和寄存器中哪些位置是引用

    所以GC時(shí)直接掃描這里就可以了。

    問題又來了....

    JIT編譯時(shí),我們?cè)摬粫?huì)在執(zhí)行過程中每一步都維護(hù)這個(gè) OopMap 吧?這樣的話得又多少個(gè) OopMap .... 我們看到上面提到了在“特定位置”,那接下來我們來看看這個(gè)“特定位置”應(yīng)該是哪里。

    現(xiàn)在給你個(gè)機(jī)會(huì),讓你來說說你怎么來設(shè)定這個(gè)“特定位置” ?

    emm...要是能有一個(gè)地方,可以讓我在更長的時(shí)間里使用同一個(gè)OopMap那就好了,這個(gè)想法不錯(cuò),哪到底哪里能夠長時(shí)間或者重復(fù)使用一個(gè) OopMap 呢?哈,循環(huán)調(diào)用的時(shí)候?。?!沒錯(cuò)了,像方法調(diào)用、循環(huán)跳轉(zhuǎn)、異常跳轉(zhuǎn)這些地方指令是可以重用的。教科書上稱這個(gè)地方為安全點(diǎn)(saftpoint)。那我們只需要記錄這些安全點(diǎn)上對(duì)應(yīng)的 OopMap 就可以咯。

    不要太開心,問題是解決不完滴.....接著看

    現(xiàn)在你在安全點(diǎn)上挖了坑等著線程往里面跳,但是我該用什么姿勢會(huì)更帥一點(diǎn)呢?

    換句話說:我就問你你喜歡我主動(dòng)點(diǎn)還是被動(dòng)點(diǎn)?


    641ce5831f0c7df383b6f77e163f5a19.webp


    好吧,老爺們?cè)趺茨苓@么被動(dòng),還得老司機(jī)扶你一把。那我們來看一下怎么扶你到點(diǎn)上吧。

    搶先式中斷:在GC時(shí),首先把所有線程中斷,如果發(fā)現(xiàn)哪些線程中斷的地方不再安全點(diǎn)上,就恢復(fù)線程,讓它“跑”到安全點(diǎn)上。當(dāng)前幾乎沒有虛擬機(jī)采用搶先式中斷來暫停線程去響應(yīng)GC了。

    不過,單生狗們,下面才是我們的標(biāo)準(zhǔn)動(dòng)作,看好咯。


    fc527825e6c91c4452ed821aa889eac6.webp


    主動(dòng)式中斷:當(dāng)GC需要中斷線程的時(shí)候,我們不直接中斷線程,而是設(shè)置一個(gè)標(biāo)志,各個(gè)線程執(zhí)行時(shí)主動(dòng)去輪詢這個(gè)標(biāo)志,發(fā)現(xiàn)是中斷標(biāo)志時(shí)就自己中斷掛起。輪詢標(biāo)志的地方和安全點(diǎn)是重合的。

    然而我們 GC 妹子還是很擔(dān)心總有那么一些渣男線程偏偏搗亂,你說該咋整呢?

    這個(gè)好辦,我把哪些賢良淑德的帥哥們給你們安排個(gè)專場,你來這里找就行了。

    安全區(qū)域:安全點(diǎn)貌似已經(jīng)解決了 GC 的問題,然后有一些線程可能沒有分配到 CPU 而無法執(zhí)行(如線程處于 Sleep 狀態(tài)或 Blocked 狀態(tài)),導(dǎo)致這些線程進(jìn)入安全點(diǎn)需要等待較長的時(shí)間。所以,為了能夠排除這些無法執(zhí)行的線程,我們給那些可以執(zhí)行的線程一個(gè)安全區(qū)域,在這個(gè)區(qū)域中任意地方開始 GC都是安全的。

    回歸主題......

    我們通過引用計(jì)數(shù)算法或可達(dá)性分析算法定位到當(dāng)前這個(gè)時(shí)刻哪些對(duì)象是可以被清理的,但是我們需要保證被標(biāo)記的對(duì)象在被清理之前不會(huì)被再次引用,所以這里出現(xiàn)了一個(gè)動(dòng)作stop the world,一個(gè)很霸氣的名字,意思就是停止所有用戶線程的執(zhí)行。什么時(shí)候發(fā)生 stop the world ? 當(dāng)然是要執(zhí)行 GC 操作的時(shí)候咯。線程停在哪里?上面那個(gè)專場(安全區(qū)域),然后現(xiàn)在就可以安心的清理垃圾了。

    在保證了不能清理掉正在被引用的對(duì)象這個(gè)基礎(chǔ)之上,我們接下來再看看我們?cè)?strong>提升收集效率和提升用戶體驗(yàn)。

    • 提升收集效率,具體化就是提高吞吐量。

    • 提升用戶體驗(yàn),具體化就是縮短回收停頓的時(shí)間

    吞吐量 = 運(yùn)行用戶代碼時(shí)間 / (運(yùn)行用戶代碼時(shí)間 + 垃圾收集時(shí)間)

    如何提高吞吐量?

    將單線程處理改為多線程處理,充分利用CPU的處理能力

    如何縮短回收停頓時(shí)間?

    在標(biāo)記期間,可以讓用戶線程和GC線程同時(shí)執(zhí)行,然后在執(zhí)行清除前在對(duì)標(biāo)記的內(nèi)存進(jìn)行一次確認(rèn),盡量縮短回收停頓的時(shí)間。

    那接下來,我們來看看 HotSpot 在收集器迭代過程中是如何提高收集效率和提升用戶體驗(yàn)的。


    2700bfdf44ed93a43df7cea8e83b483c.webp


    1)Serial 收集器

    • jdk1.3.1之前版本

    • 新生代的收集器

    • 采用復(fù)制算法

    • 單線程收集器,進(jìn)行垃圾收集時(shí)暫停所有用戶線程

    優(yōu)點(diǎn):在單個(gè)CPU環(huán)境下運(yùn)行簡單高效

    缺點(diǎn):無法充分利用多個(gè)CPU的處理能力


    8f902766458c976b310bbafbd3a7a165.webp


    2)ParNew 收集器

    可以看做是Serial收集器的多線程版本

    • 新生代收集器

    • 采用復(fù)制算法

    • 多線程收集器,進(jìn)行垃圾收集時(shí)暫停所有用戶線程

    優(yōu)點(diǎn):在多核CPU環(huán)境下要比Serial收集器效率高。

    缺點(diǎn):CPU個(gè)數(shù)較少情況下不明顯,甚至可能比Serial收集器效率還要低


    ae0c149c803b3b62aab2650ec4da3763.webp


    3)Parallel Scavenge 收集器

    • 新生代收集器

    • 采用復(fù)制算法

    • 多線程收集器,進(jìn)行垃圾收集時(shí)暫停所有用戶線程

    和ParNew收集器的區(qū)別:

    Paralle Scavenge 收集器的目標(biāo)是達(dá)到一個(gè)可控制的吞吐量(CPU用于用戶代碼的時(shí)間與CPU總消耗時(shí)間的比值)

    吞吐量 = 運(yùn)行用戶代碼時(shí)間 / (運(yùn)行用戶代碼時(shí)間 + 垃圾收集時(shí)間)

    兩個(gè)重要參數(shù)設(shè)置:

    • -XX:MaxGCPauseMills ? 參數(shù)說明:控制垃圾收集停頓時(shí)間,參數(shù)值的設(shè)定并非越小越好,參數(shù)值較小,垃圾收集速度較快,則會(huì)犧牲吞吐量和新生代空間

    • -XX:GCTimeRatio ?參數(shù)說明:設(shè)置垃圾收集時(shí)間占總時(shí)間的比率,即吞吐量=1-垃圾收集時(shí)間占總時(shí)間的比率

    優(yōu)點(diǎn):可以提供良好的響應(yīng)速度,從而提升用戶體驗(yàn)

    缺點(diǎn):需要減少新生代空間來以及降低吞吐量來縮短停頓時(shí)間


    a0474fa999c94249a20084c4a78286de.webp


    4)Serial Old收集器

    • 老年代收集器

    • 采用標(biāo)記-整理算法

    • 單線程收集器,進(jìn)行垃圾收集時(shí)暫停所有用戶線程

    • 用于Client模式下的虛擬機(jī)使用

    兩大用途:

    • JDK1.5 以及之前的版本中與Parallel Scavenge 收集器搭配使用

    • 作為 CMS 收集器的后備預(yù)案,在并發(fā)收集發(fā)生Concurrent Mode Failure 時(shí)使用


    8f902766458c976b310bbafbd3a7a165.webp


    5)Parallel Old 收集器

    • JDK 1.6版本提供使用

    • 老年代收集器

    • 采用標(biāo)記-整理算法

    • 多線程收集器,進(jìn)行垃圾收集時(shí)暫停所有用戶線程

    優(yōu)點(diǎn):可以充分利用服務(wù)器多CPU的處理能力


    a0474fa999c94249a20084c4a78286de.webp


    6)CMS 收集器

    • 老年代收集器

    • 采用標(biāo)記-清除算法

    • 多線程收集器

    與 Parallel Old 收集器的區(qū)別:

    CMS 收集器目標(biāo)是獲取最短回收停頓時(shí)間。

    處理過程:

    • 初始標(biāo)記(CMS initial mark)

    • 并發(fā)標(biāo)記(CMS concurrent mark)

    • 重新標(biāo)記(CMS remark)

    • 并發(fā)清除(CMS concurrent sweep)

    在這里我需要簡單說明一下:所有在堆中的對(duì)象都是記錄的,我們可以暫且看作一個(gè)記錄表,那么在這個(gè)記錄表中,如果標(biāo)記了所有被引用的對(duì)象,那么反過來我們就自然知道那些沒有被標(biāo)記的對(duì)象就是可回收對(duì)象,有時(shí)候我們說標(biāo)記了對(duì)象是指標(biāo)記了可回收對(duì)象。

    從下面示意圖中,有沒有發(fā)現(xiàn)這樣一個(gè)問題,就是在標(biāo)記和清除的過程中,并不是一直都處于 stop the world,當(dāng)時(shí)我產(chǎn)生了一個(gè)疑問,那在這個(gè)過程中會(huì)不會(huì)存在錯(cuò)誤的把那些被引用的對(duì)象當(dāng)作垃圾對(duì)象的情況呢?我們看到標(biāo)記階段被分為了三次執(zhí)行:

    1. 第一次標(biāo)記,僅僅是標(biāo)記了與 GC Roots 直接關(guān)聯(lián)的對(duì)象。且會(huì) stop the world

    2. 第二次標(biāo)記,將第一次標(biāo)記的對(duì)象進(jìn)行追溯。這個(gè)過程不會(huì) stop the world,而是與用戶線程并發(fā)執(zhí)行的,所以可能會(huì)產(chǎn)生這樣兩個(gè)問題,一是可能有些被標(biāo)記的對(duì)象是被引用了的,二是可能又產(chǎn)生了一些新的垃圾。針對(duì)于“可能有些被標(biāo)記的對(duì)象是被引用了的”情況我們可以通過第三次標(biāo)記來處理。而對(duì)于“可能又產(chǎn)生了一些新的垃圾”的情況我們可以在下次在對(duì)其清理,并不影響我本次 GC 的準(zhǔn)確性,這個(gè)階段產(chǎn)生的垃圾在教科書中被成為“浮動(dòng)垃圾”。

    3. 第三次標(biāo)記,會(huì)修正第二次并發(fā)標(biāo)記過程中因?yàn)橛脩艟€程執(zhí)行而導(dǎo)致標(biāo)記變動(dòng)的對(duì)象。這個(gè)階段耗時(shí)比第一次標(biāo)記要長,但是遠(yuǎn)比第二次標(biāo)記耗時(shí)要短。

    你可能你存在一個(gè)疑問:第二次標(biāo)記為什么會(huì)產(chǎn)生“可能有些被標(biāo)記的對(duì)象是被引用了的”?

    因?yàn)槲覀兺ㄟ^可達(dá)性分析算法追溯那些對(duì)象被引用時(shí),只是根據(jù)第一次標(biāo)記時(shí)與 GC Roots 直接關(guān)聯(lián)的對(duì)象進(jìn)行追溯的,但是在第二個(gè)階段用戶線程也在執(zhí)行,這個(gè)時(shí)候 GC Roots中會(huì)產(chǎn)生新的對(duì)象,也就會(huì)產(chǎn)生新的鏈路,那么我們并沒有對(duì)這些鏈路去追溯對(duì)象,所以那些對(duì)象也就被我們標(biāo)記而被是為垃圾對(duì)象,所以這個(gè)時(shí)候,需要第三次標(biāo)記,把這段時(shí)間新產(chǎn)生的 GC Roots 在進(jìn)行一次追溯,修正一下被誤認(rèn)為是垃圾的對(duì)象。由于第二次并發(fā)標(biāo)記執(zhí)行的時(shí)間產(chǎn)生新的 GC Roots 是要比第一次少的多的,所以第三次重新標(biāo)記的時(shí)間要短的多。

    那么在上面我們已經(jīng)標(biāo)記了哪些對(duì)象為垃圾對(duì)象了,那么回收階段我們就只管回收就好了,是否與用戶線程并發(fā)執(zhí)行并沒有關(guān)系。好吧,也會(huì)你會(huì)問為什么會(huì)沒有關(guān)系呢?因?yàn)楫?dāng)某一個(gè)時(shí)刻,這個(gè)對(duì)象在全局范圍內(nèi)沒有被使用,那么后續(xù)過程怎么會(huì)使用呢?相當(dāng)于這個(gè)引用的傳遞關(guān)系已經(jīng)就此斷開了呀。

    通過上面的分析我們也能夠知道 CMS 收集器存在哪些缺點(diǎn)了吧。主要有兩個(gè)缺點(diǎn)

    • 無法處理“浮動(dòng)垃圾”,只能等到第二次觸發(fā)GC時(shí)進(jìn)行清理

    • CMS既然采用的是標(biāo)記-清除算法,那么必然會(huì)產(chǎn)生大量的空間碎片


    fb7292e3de9f85893f3b019a72181051.webp


    7)G1 收集器

    面向服務(wù)端應(yīng)用的垃圾收集器。

    處理過程

    • 初始標(biāo)記

    • 并發(fā)標(biāo)記

    • 最終標(biāo)記

    • 篩選回收

    特點(diǎn)

    • 并行與并發(fā)

    • 分代收集

    • 空間整合

    • 可預(yù)測的停頓

    接下來,我們來看兩個(gè)問題,一是 G1 收集器和 CMS 收集器的區(qū)別,二是 G1 收集器的特點(diǎn)具體是什么。

    問題1:G1 收集器和 CMS 收集器的區(qū)別

    在處理過程中,G1 收集器和 CMS 收集器的差別并不是很大,僅僅是在回收時(shí),CMS 收集器是并發(fā)執(zhí)行,而 G1 收集器是篩選回收。那我們來看一下它們?cè)诨厥针A段到底有哪些區(qū)別。

    • CMS 收集器通過并發(fā)執(zhí)行,將標(biāo)記階段標(biāo)記的所有垃圾對(duì)象進(jìn)行了回收。

    • G1 收集器通過篩選回收,可以選擇部分垃圾對(duì)象進(jìn)行回收。

    兩者的差別就是一個(gè)是只能全部垃圾回收,一個(gè)可以部分垃圾回收。

    那么部分垃圾回收有哪些好處呢?或者我們可以先考慮一下 G1 是如何做到部分垃圾回收的。

    G1 收集器和 CMS 收集器的關(guān)注點(diǎn)都是追求低停頓,但是 G1 收集器通過將 Java 堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域,然后有計(jì)劃的去對(duì)各個(gè)區(qū)域進(jìn)行收集,從而達(dá)到對(duì)垃圾收集時(shí)間的一個(gè)控制。

    接踵而來的問題......

    劃分成大小相等的獨(dú)立區(qū)域,難道區(qū)域之間是真的獨(dú)立而沒有任何交集嗎?

    我們知道 HotSpot 虛擬機(jī)將 Java 堆分為新生代和老年代,上面提到的收集器也是分別針對(duì)于新生代和老年代進(jìn)行的處理。但是 G1 收集器可以同時(shí)處理新生代和老年代,所以對(duì)于 G1 收集器,Java 堆的布局也是發(fā)生了變化的。Java 堆被劃分為多個(gè)大小相等的獨(dú)立區(qū)域,新生代和老年代也變成了只是邏輯上的區(qū)分,在物理上不再隔離了。

    在回答區(qū)域之間是否是真的獨(dú)立而沒有交集這個(gè)問題前,我想問一下,新生代和老年代之間交集嗎?毋庸置疑,肯定是有交集啊,我只不過是把對(duì)象從新生代挪到老年代而已嘛。那好,我再問一個(gè)問題,那我們?cè)趯?duì)新生代進(jìn)行 GC 時(shí),也就是我們說的 Minor GC 時(shí),我們還要把老年代也給遍歷一遍嗎?如果真的需要遍歷一遍,那估計(jì)能把人等死....

    其實(shí),新生代和老年代之間的對(duì)象引用,虛擬機(jī)通過使用 Remembered Set 記錄下來了,從而避免了全堆掃描。那現(xiàn)在回到上面那個(gè)問題上來,你說 G1 收集器劃分的各個(gè)區(qū)域之間有沒有交集呢?如果有它們之間是怎么處理的呢?現(xiàn)在應(yīng)該不用我再來解釋了吧。

    問題2: G1 收集器的特點(diǎn)具體是什么?

    1)并行與并發(fā)

    充分利用多CPU、多核的處理能力來縮短 Stop-The-World 停頓時(shí)間。

    2)分代收集

    與其他收集器的概念相同。

    3)空間整合

    從整體來看是基于標(biāo)記-整理算法實(shí)現(xiàn)的垃圾收集。

    從局部(兩個(gè)Region之間)上來看是基于復(fù)制算法實(shí)現(xiàn)的垃圾收集。

    因此,G1 收集器在垃圾收集期間不會(huì)產(chǎn)生內(nèi)存空間碎片。

    4)可預(yù)測的停頓

    G1會(huì)跟蹤各個(gè)區(qū)域里面的垃圾堆積的價(jià)值大?。ɑ厥账@得空間大小記憶回收所需時(shí)間的經(jīng)驗(yàn)值),在后臺(tái)維護(hù)一個(gè)優(yōu)先列表,根據(jù)設(shè)定的允許收集時(shí)間,優(yōu)先回收價(jià)值最大的區(qū)域。


    58c1cc8ae71ac95ad1d35e6ad1adb103.webp


    5、什么時(shí)候回收垃圾?

    什么時(shí)候回收垃圾?

    換種說法就是什么時(shí)候回收內(nèi)存?

    好了,請(qǐng)問你什么時(shí)候找你朋友借錢或者還錢?----錢不夠用的時(shí)候

    同樣道理,當(dāng)內(nèi)存不夠用的時(shí)候就要進(jìn)行回收了。

    既然提到內(nèi)存不夠用了,那你得直到內(nèi)存是怎么用了吧,就像是你錢沒了你得知道錢怎么花了吧。不過話說回來,這方面得學(xué)學(xué)人家內(nèi)存,大部分都花在對(duì)象身上了。那我們就說說對(duì)象是怎么“花錢”的吧。

    內(nèi)存分配

    對(duì)象的內(nèi)存分配主要是在堆上分配。對(duì)象優(yōu)先在新生代的 Eden 區(qū)上,少數(shù)情況也會(huì)直接分配到老年代中,分配規(guī)則是可以通過配置參數(shù)進(jìn)行調(diào)整。我們接下來看一下通常情況下內(nèi)存的分配規(guī)則。

    1)對(duì)象優(yōu)先在 Eden 分配

    大多數(shù)情況下,對(duì)象在新生代 Eden 區(qū)中分配。當(dāng) Eden 區(qū)空間不足時(shí)觸發(fā) Minor GC。

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

    所謂大對(duì)象是指需要大量連續(xù)內(nèi)存空間的對(duì)象,比如很長的字符串或數(shù)組。

    虛擬機(jī)提供了 -XX:PretenureSizeThreshold 參數(shù),令大于該參數(shù)值的對(duì)象直接進(jìn)入老年代。避免在 Eden 區(qū)和兩個(gè) Survivor 區(qū)之間發(fā)生大量的內(nèi)存復(fù)制。

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

    在內(nèi)存回收時(shí),虛擬機(jī)需要識(shí)別哪些對(duì)象應(yīng)該放在新生代,哪些對(duì)象應(yīng)該放在老年代。虛擬機(jī)給每個(gè)對(duì)象定義了一個(gè)對(duì)象年齡計(jì)數(shù)器。對(duì)象在 Eden 出生并經(jīng)過第一次 Minor GC 后仍然存活,并且能夠被 Survivor 容納,則被移動(dòng)到 Survivor 區(qū),且對(duì)象年齡設(shè)為1。對(duì)象在 Survivor 區(qū)每經(jīng)過一次 Minor GC, 年齡加1。當(dāng)年齡增長到一定程度(默認(rèn)為15歲),將會(huì)被放到老年代。

    虛擬機(jī)提供了 -XX:MaxTenuringThreshold 參數(shù),設(shè)置對(duì)象進(jìn)入老年代的年齡閾值。

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

    如果在 Survivor 區(qū)中相同年齡所有對(duì)象大小的總和大于 Survivor 空間的一半,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代。

    5)空間分配擔(dān)保

    在前面我們講到,新生代采用的是復(fù)制算法。如 HotSpot 虛擬機(jī)中,將新生代分為 Eden 區(qū)和兩個(gè) Survivor 區(qū),默認(rèn)比例是8:1。每次 Minor GC 時(shí)會(huì)講 Eden 區(qū)和 一個(gè) Survivor 區(qū)中存活的對(duì)象拷貝到另一個(gè) Survivor 區(qū)。那么可能存在這么一種情況,就是另一Survivor 區(qū)的空間不足一容納 Eden 區(qū)和 Survivor 區(qū)中存活的對(duì)象,這個(gè)時(shí)候該怎么辦?這里就是我們要說的空間分配擔(dān)保,如果上面發(fā)生了上面說的那種情況,那么在 Eden 區(qū)和 Survivor 區(qū)存活的對(duì)象將直接進(jìn)入老年代,即老年代為 Survivor 區(qū)進(jìn)行擔(dān)保。

    而老年代采用的是標(biāo)記-整理算法,無需其他空間擔(dān)保。當(dāng)老年代空間不足時(shí),觸發(fā)一次 Full GC。

    現(xiàn)在來看什么時(shí)候進(jìn)行垃圾回收就比較清晰了,當(dāng)新生代空間不足以為對(duì)象分配空間時(shí),觸發(fā)一次 Minor GC,當(dāng)老年代空間不足以為對(duì)象分配空間時(shí),觸發(fā)一次 Full GC。


    作者:雨陽
    鏈接:https://zhuanlan.zhihu.com/p/82936943
    來源:知乎

    end


    *版權(quán)聲明:轉(zhuǎn)載文章和圖片均來自公開網(wǎng)絡(luò),版權(quán)歸作者本人所有,推送文章除非無法確認(rèn),我們都會(huì)注明作者和來源。如果出處有誤或侵犯到原作者權(quán)益,請(qǐng)與我們聯(lián)系刪除或授權(quán)事宜。


    長按識(shí)別圖中二維碼

    關(guān)注獲取更多資訊




    不點(diǎn)關(guān)注,我們哪來故事?



    94f0d1bf1dbe14e8d43aa041e053075d.webp

    點(diǎn)個(gè)再看,你最好看




    瀏覽 87
    點(diǎn)贊
    評(píng)論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報(bào)
    評(píng)論
    圖片
    表情
    推薦
    點(diǎn)贊
    評(píng)論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報(bào)

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    思思99re| 国产免费一区二区三区在线 | 成人一区二区三区四区五区91电影 | 精品人妻在线 | 男人av资源在线 欧美操逼免费观看 |