1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        我肝了萬字的Java垃圾回收,看完你還敢說不會?

        共 7156字,需瀏覽 15分鐘

         ·

        2021-01-09 16:35

        今天來說說 Java 垃圾回收,高頻面試問題。

        提綱附上,話不多說,直接干貨

        1、什么是垃圾回收?

        垃圾回收(Garbage Collection,GC):就是釋放垃圾占用的空間,防止內(nèi)存泄露。對內(nèi)存堆中已經(jīng)死亡的或者長時(shí)間沒有使用的對象進(jìn)行清除和回收。

        2、垃圾在哪兒?

        上圖可以看到程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧都是伴隨著線程而生死,這些區(qū)域不需要進(jìn)行 GC。

        而方法區(qū)/元空間在 1.8 之后就直接放到本地內(nèi)存了,假設(shè)總內(nèi)存 2G,JVM 被分配內(nèi)存 100M, 理論上元空間可以分配 2G-100M = 1.9G,空間還是足夠的,所以這塊區(qū)域也不用管。

        所以就只剩下了,java 對象實(shí)例和數(shù)組都是在上分配的,所以垃圾回收器重點(diǎn)照顧

        3、怎么發(fā)現(xiàn)它?

        在發(fā)生 GC 的時(shí)候,Jvm 是怎么判斷堆中的對象實(shí)例是不是垃圾呢?

        這里有兩種方式:

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

        就是給對象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器的值就加 1,每當(dāng)有一個(gè)引用失效時(shí),計(jì)數(shù)器的值就減 1。任何時(shí)刻只要對象的計(jì)數(shù)器值為 0,那么就可以被判定為垃圾對象。

        這種方式,效率挺高,但是 Jvm 并沒有使用引用計(jì)數(shù)算法。那是因?yàn)樵谀撤N場合下存在問題

        比如下面的代碼,會出現(xiàn)循環(huán)引用的問題:

        public?class?Test?{
        ????Test?test;
        ????public?Test(String?name)?{}

        ????public?static??void?main(String[]?args)?{
        ????????Test?a?=?new?Test("A");
        ????????Test?b?=?new?Test("B");

        ????????a.test?=?b;
        ????????b.test?=?a;

        ????????a?=?null;
        ????????b?=?null;
        ????}
        }

        即使你把 a 和 b 的引用都置為 null 了,計(jì)數(shù)器也不是 0,而是 1,因?yàn)樗鼈冎赶虻膶ο笥只ハ嘀赶蛄藢Ψ?,所以無法回收這兩個(gè)對象。

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

        這才是 jvm 默認(rèn)使用的尋找垃圾算法。

        它的原理是通過一些列稱為“GC Roots”的對象作為起始點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,搜素所走過的路叫做稱為引用鏈“Reference Chain”,當(dāng)一個(gè)對象到 GC Roots 沒有任何引用鏈時(shí),就說這個(gè)對象是不可達(dá)的。

        從上圖可以看到,即使 Object5 和 Object6 之間相互引用,但是沒有 GC Roots 和它們關(guān)聯(lián),所以可以解決循環(huán)引用的問題。

        小知識點(diǎn):

        1、哪些可以作為 GC ROOTS 根呢?

        1. 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象
        2. 方法區(qū)中類靜態(tài)屬性引用的對象
        3. 方法區(qū)中常量引用的對象
        4. 本地方法棧中 JNI(即一般說的 Native 方法)引用的對象

        2、不得不說的四種引用

        1. 強(qiáng)引用:就是在程序中普遍存在的,類似“Object a=new Object”這類的引用。只要強(qiáng)引用關(guān)系還存在,垃圾回收器就不會回收掉被引用的對象。
        2. 軟引用:用來描述一些還有用但是并非必須的對象。直到內(nèi)存空間不夠時(shí)(拋出 OutOfMemoryError 之前),才會被垃圾回收,通過 SoftReference 來實(shí)現(xiàn)。
        3. 弱引用:比軟引用還弱,也是用來描述非必須的對象的,當(dāng)垃圾回收器開始工作時(shí),無論內(nèi)存是否足夠用,弱引用的關(guān)聯(lián)的對象都會被回收 WeakReference。
        4. 虛引用:它是最弱的一種引用關(guān)系,它的唯一作用是用來作為一種通知。采用 PhantomRenference 實(shí)現(xiàn)

        3、為什么定義這些引用?

        個(gè)人理解,其實(shí)就是給對象加一種中間態(tài),讓一個(gè)對象不只有引用和非引用兩種情況,還可以描述一些“食之無味棄之可惜”的對象。比如說:當(dāng)內(nèi)存空間足時(shí),則能保存在內(nèi)存中,如果內(nèi)存空間在進(jìn)行垃圾回收之后還不夠時(shí),才對這些對象進(jìn)行回收。

        4、生存還是死亡?

        要真正宣告一個(gè)對象死亡,至少要經(jīng)歷兩次標(biāo)記過程和一次篩選。

        一張圖帶你看明白:

        5、垃圾收集算法

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

        分為兩個(gè)階段“標(biāo)記”和“清除”,標(biāo)記出所有要回收的對象,然后統(tǒng)一進(jìn)行清除。

        缺點(diǎn):

        1. 在對象變多的情況下,標(biāo)記和清除效率都不高
        2. 會產(chǎn)生空間碎片

        2、復(fù)制算法

        就是將堆分成兩塊完全相同的區(qū)域,對象只在其中一塊區(qū)域內(nèi)分配,然后標(biāo)記出那些是存活的對象,按順序整體移到另外一個(gè)空間,然后回收掉之前那個(gè)區(qū)域的所有對象。

        缺點(diǎn):

        1. 雖然能夠解決空間碎片的問題,但是空間少了一半。也太多了吧??!

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

        這種算法是,先找到存活的對象,然后將它們向空間的一端移動,最后回收掉邊界以外的垃圾對象。

        4、分代收集

        其實(shí)就是整合了上面三種算法,揚(yáng)長避短。

        之所以叫分代,是因?yàn)楦鶕?jù)對象存活周期的不同將整個(gè) Java 堆切割成為三個(gè)部分:

        • Young(年輕代)
          • Eden(伊利園):新生對象
          • Survivor(幸存者):垃圾回收后還活著的對象
        • Tenured(老年代):對象多次回收都沒有被清理,會移到老年代
        • Perm(永久代):存放加載的類別還有方法對象,java8 之后移除了永久代,替換為元空間(Metaspace)

        在新生代中,每次垃圾收集都有大量的對象死去,只有少量的存活,那就選用 復(fù)制算法 ,因?yàn)閺?fù)制成本很小,只需要復(fù)制少量存活對象。

        老年代中,存活對象較多,沒有額外的空間擔(dān)保,就得使用 標(biāo)記清除 或者 標(biāo)記整理

        6、垃圾收集器

        在說垃圾回收器之前需要了解幾個(gè)概念:

        1、幾個(gè)概念

        吞吐量

        CPU 用于運(yùn)行用戶代碼的時(shí)間與 CPU 總消耗時(shí)間的比值。

        比如說虛擬機(jī)總運(yùn)行了 100 分鐘,用戶代碼時(shí)間 99 分鐘,垃圾回收時(shí)間 1 分鐘,那么吞吐量就是 99%。

        STW

        全稱 Stop-The-World,即在 GC 期間,只有垃圾回收器線程在工作,其他工作線程則被掛起。

        為什么需要 STW 呢?

        在 java 程序中引用關(guān)系是不斷會變化的,那么就會有很多種情況來導(dǎo)致垃圾標(biāo)識出錯。

        想想一下如果一個(gè)對象 A 當(dāng)前是個(gè)垃圾,GC 把它標(biāo)記為垃圾,但是在清除前又有其他引用指向了 A,那么此刻又不是垃圾了。

        那么,如果沒有 STW 的話,就要去無限維護(hù)這種關(guān)系來去采集正確的信息,顯然是不可取的。

        安全點(diǎn)

        從線程角度看,安全點(diǎn)可以理解成是在代碼執(zhí)行過程中的一些特殊位置,當(dāng)線程執(zhí)行到這些位置的時(shí)候,說明虛擬機(jī)當(dāng)前的狀態(tài)是安全的。

        比如:方法調(diào)用、循環(huán)跳轉(zhuǎn)異常跳轉(zhuǎn)等這些地方才會產(chǎn)生安全點(diǎn)。

        如果有需要,可以在這個(gè)位置暫停,比如發(fā)生 GC 時(shí),需要暫停所有活動線程,但是線程在這個(gè)時(shí)刻,還沒有執(zhí)行到一個(gè)安全點(diǎn),所以該線程應(yīng)該繼續(xù)執(zhí)行,到達(dá)下一個(gè)安全點(diǎn)的時(shí)候暫停,等待 GC 結(jié)束。

        串行、并行

        串行:是指垃圾回收線程在進(jìn)行垃圾回收工作,此時(shí)用戶線程處于等待狀態(tài)。

        并行:是指用戶線程和多條垃圾回收線程分別在不同 CPU 上同時(shí)工作。

        2、回收器

        下面是一張很經(jīng)典的圖,展示了 7 種不同分代的收集器,如果兩個(gè)收集器之間存在連線,說明可以搭配使用。

        Serial

        Serial 收集器是一個(gè)單線程收集器,在進(jìn)行垃圾回收器的時(shí)候,必須暫停其他工作線程,也就是發(fā)生 STW。在 GC 期間,應(yīng)用是不可用的。

        特點(diǎn):1、采用復(fù)制算法 ?2、單線程收集器 ?3、效率會比較慢,但是因?yàn)槭菃尉€程,所以消耗內(nèi)存小

        ParNew

        ParNew 是 Serial 的多線程版本,也是工作在新生代,能與 CMS 配合使用。

        在多 CPU 的情況下,由于 ParNew 的多線程回收特性,毫無疑問垃圾收集會更快,也能有效地減少 STW 的時(shí)間,提升應(yīng)用的響應(yīng)速度。

        特點(diǎn):1、采用復(fù)制算法 ?2、多線程收集器 ?3、效率高,能大大減少 STW 時(shí)間。

        Parallel Scavenge

        Parallel Scavenge 收集器也是一個(gè)使用復(fù)制算法,多線程,工作于新生代的垃圾收集器,看起來功能和 ParNew 收集器基本一樣。

        但是它有啥特別之處呢?關(guān)注點(diǎn)不同

        • ParNew 垃圾收集器關(guān)注的是盡可能縮短垃圾收集時(shí)用戶線程的停頓時(shí)間,更適合用到與用戶交互的程序,因?yàn)橥nD時(shí)間越短,用戶體驗(yàn)肯定就好呀??!
        • Parallel Scavenge 目標(biāo)是達(dá)到一個(gè)可控制的吞吐量,所以更適合做后臺運(yùn)算等不需要太多用戶交互的任務(wù)。

        Parallel Scavenge 收集器提供了兩個(gè)參數(shù)來控制吞吐量,

        • -XX:MaxGCPauseMillis:控制最大垃圾收集時(shí)間
        • -XX:GCTimeRati:直接設(shè)置吞吐量大小

        特點(diǎn):1、采用復(fù)制算法 2、多線程收集器 3、吞吐量優(yōu)先

        Serial Old

        Serial 收集器是工作于新生代的單線程收集器,與之相對地,Serial Old 是工作于老年代的單線程收集器。

        作用:

        • 在 Client 模式下與 Serial 回收器配合使用
        • Server 模式下,則它還有兩大用途:一種是在 JDK 1.5 及之前的版本中與 Parallel Scavenge 配合使用,另一種是作為 CMS 收集器的后備預(yù)案,在并發(fā)收集發(fā)生 Concurrent Mode Failure 時(shí)使用

        它與 Serial 收集器配合使用示意圖如下:

        特點(diǎn):1、標(biāo)記-整理算法 2、單線程 3、老年代工作

        Parallel Old

        Parallel Old 是一個(gè)多線程的垃圾回收器,采用標(biāo)記整理算法,負(fù)責(zé)老年代的垃圾回收工作,可以與 Parallel Scavenge 垃圾回收器一起搭配工作。真正的實(shí)現(xiàn)吞吐量優(yōu)先

        示意圖如下:

        特點(diǎn):1、標(biāo)記-整理算法 2、多線程 3、老年代工作

        CMS

        CMS 可以說是一款具有"跨時(shí)代"意義的垃圾回收器,如果應(yīng)用很重視服務(wù)的響應(yīng)速度,希望給用戶最好的體驗(yàn),則 CMS 收集器是非常合適的,它是以獲取最短回收停頓時(shí)間為目標(biāo)的收集器!

        CMS 雖然工作在老年代,和之前收集器不同的是,使用的標(biāo)記清除算法

        示意圖如下:

        垃圾回收的 4 個(gè)步驟:

        1. 初始標(biāo)記:標(biāo)記出來和 GC Roots 直接關(guān)聯(lián)的對象,整個(gè)速度是非??斓模瑫l(fā)生 STW,確保標(biāo)記的準(zhǔn)確性。
        2. 并發(fā)標(biāo)記:并發(fā)標(biāo)記這個(gè)階段會直接根據(jù)第一步關(guān)聯(lián)的對象找到所有的引用關(guān)系,耗時(shí)較長,但是這個(gè)階段會與用戶線程并發(fā)運(yùn)行,不會有很大的影響。
        3. 重新標(biāo)記:這個(gè)階段是為了解決第二步并發(fā)標(biāo)記所導(dǎo)致的標(biāo)錯情況。并發(fā)階段會和用戶線程并行,有可能會出現(xiàn)判斷錯誤的情況,這個(gè)階段就是對上一個(gè)階段的修正。
        4. 并發(fā)清除:最后一個(gè)階段,將之前確認(rèn)為垃圾的對象進(jìn)行回收,會和用戶線程一起并發(fā)執(zhí)行。

        缺點(diǎn):

        1. 影響用戶線程的執(zhí)行效率:CMS 默認(rèn)啟動的回收線程數(shù)是(處理器核心數(shù) + 3)/ 4 ,由于是和用戶線程一起并發(fā)清理,那么勢必會影響到用戶線程的執(zhí)行速度
        2. 會產(chǎn)生浮動垃圾:CMS 的第 4 個(gè)階段并發(fā)清除是和用戶線程一起的,會產(chǎn)生新的垃圾,就叫浮動垃圾
        3. 會產(chǎn)生碎片化的空間:標(biāo)記清除的缺點(diǎn)

        G1

        全稱:Garbage-First

        G1 回收的目標(biāo)不再是整個(gè)新生代或者是老年代。G1 可以回收堆內(nèi)存的任何空間來進(jìn)行,不再是根據(jù)年代來區(qū)分,而是那塊空間垃圾多就去回收,通過 Mixed GC 的方式去進(jìn)行回收。

        先看下堆空間的劃分:

        G1 垃圾回收器把堆劃分成大小相同的 Region,每個(gè) Region 都會扮演一個(gè)角色,分別為 H、S、E、O。

        1. E 代表伊甸區(qū)
        2. S 代表 Survivor 區(qū)
        3. H 代表的是 Humongous 區(qū)
        4. O 代表 Old 區(qū)

        G1 的工作流程圖:

        • 初始標(biāo)記:標(biāo)記出來 GC Roots 能直接關(guān)聯(lián)到的對象,修改 TAMS 的值以便于并發(fā)回收時(shí)新對象分配
        • 并發(fā)標(biāo)記:根據(jù)剛剛關(guān)聯(lián)的對像掃描整個(gè)對象引用圖,和用戶線程并發(fā)執(zhí)行,記錄 SATB(原始快照) 在并發(fā)時(shí)有引用的值
        • 最終標(biāo)記:處理第二步遺留下來的少量 SATB(原始快照) 記錄,會發(fā)生 STW
        • 篩選回收:維護(hù)之前提到的優(yōu)先級列表,根據(jù)優(yōu)先級列表、用戶設(shè)置的最大暫停時(shí)間來回收 Region

        特點(diǎn):

        1. 并行與并發(fā):G1 能充分利用多 CPU、多核環(huán)境下的硬件優(yōu)勢,可以通過并發(fā)的方式讓 Java 程序繼續(xù)執(zhí)行,進(jìn)一步縮短 STW 的時(shí)間。
        2. 分代收集:分代概念在 G1 中依然得以保留,它能夠采用不同的方式去處理新創(chuàng)建的對象和已經(jīng)存活了一段時(shí)間、熬過多次 GC 的舊對象來獲得更好的收集效果。
        3. 空間整合:G1 從整體上看是基于標(biāo)記-整理算法實(shí)現(xiàn)的,從局部(兩個(gè) Region 之間)上看是基于復(fù)制算法實(shí)現(xiàn)的,G1 運(yùn)行期間不會產(chǎn)生內(nèi)存空間碎片。
        4. 可預(yù)測停頓:G1 比 CMS 厲害在能建立可預(yù)測的停頓時(shí)間模型,能讓使用者明確指定在一個(gè)長度為 M 毫秒的時(shí)間片段內(nèi),消耗在垃圾收集上的時(shí)間不得超過 N 毫秒。

        7、內(nèi)存分配與回收策略

        上文說的一直都是回收內(nèi)存的內(nèi)容,那么怎么給對象分配內(nèi)存呢?

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

        Eden 區(qū)

        研究表明,有將近 98%的對象是朝生夕死,所以針對這一現(xiàn)狀,大多數(shù)情況下,對象會在新生代 Eden 區(qū)中進(jìn)行分配。

        當(dāng) Eden 區(qū)沒有足夠空間進(jìn)行分配時(shí),虛擬機(jī)會發(fā)起一次 Minor GC,Minor GC 相比 Major GC 更頻繁,回收速度也更快。

        通過 Minor GC 之后,Eden 會被清空,Eden 區(qū)中絕大部分對象會被回收,而那些無需回收的存活對象,將會進(jìn)到 Survivor 的 From 區(qū)(若 From 區(qū)不夠,則直接進(jìn)入 Old 區(qū))。

        Survivor 區(qū)

        Survivor 區(qū)相當(dāng)于是 Eden 區(qū)和 Old 區(qū)的一個(gè)緩沖,Survivor 又分為 2 個(gè)區(qū),一個(gè)是 From 區(qū),一個(gè)是 To 區(qū)。每次執(zhí)行 Minor GC,會將 Eden 區(qū)和 From 存活的對象放到 Survivor 的 To 區(qū)(如果 To 區(qū)不夠,則直接進(jìn)入 Old 區(qū))。

        問題 1:為什么需要 Survivor?

        如果沒有 Survivor 區(qū),Eden 區(qū)每進(jìn)行一次 Minor GC,存活的對象就會被送到老年代,老年代很快就會被填滿。而有很多對象雖然一次 Minor GC 沒有消滅,但其實(shí)或許第二次,第三次就需要被清除。

        這時(shí)候移入老年區(qū),很明顯不是一個(gè)明智的決定。

        所以,Survivor 的存在意義就是減少被送到老年代的對象,進(jìn)而減少老年代 GC 的發(fā)生。Survivor 的預(yù)篩選保證,只有經(jīng)歷 15 次 Minor GC 還能在新生代中存活的對象,才會被送到老年代。

        問題 2:為什么需要 From 和 To 兩個(gè)呢?

        這種機(jī)制最大的好處就是可以解決內(nèi)存碎片化,整個(gè)過程中,永遠(yuǎn)有一個(gè) Survivor 區(qū)是空的,另一個(gè)非空的 Survivor 區(qū)是無碎片的。

        假設(shè)只有一個(gè) Survivor 區(qū)。

        Minor GC 執(zhí)行后,Eden 區(qū)被清空了,存活的對象放到了 Survivor 區(qū),而之前 Survivor 區(qū)中的對象,可能也有一些是需要被清除的。

        那么問題來了,這時(shí)候我們怎么清除它們?

        在這種場景下,我們只能標(biāo)記清除,而我們知道標(biāo)記清除最大的問題就是內(nèi)存碎片,在新生代這種經(jīng)常會消亡的區(qū)域,采用標(biāo)記清除必然會讓內(nèi)存產(chǎn)生嚴(yán)重的碎片化。

        因?yàn)?Survivor 有 2 個(gè)區(qū)域,所以每次 Minor GC,會將之前 Eden 區(qū)和 From 區(qū)中的存活對象復(fù)制到 To 區(qū)域。第二次 Minor GC 時(shí),To 區(qū) 到 From 區(qū) ,以此反復(fù)。

        Old 區(qū)

        老年代占據(jù)著 2/3 的堆內(nèi)存空間,只有在 Major GC 的時(shí)候才會進(jìn)行清理,每次 GC 都會觸發(fā)“Stop-The-World”。內(nèi)存越大,STW 的時(shí)間也越長,所以內(nèi)存也不僅僅是越大就越好。

        由于復(fù)制算法在對象存活率較高的老年代會進(jìn)行很多次的復(fù)制操作,效率很低,所以在這里老年代采用的是標(biāo)記整理算法。

        下面三種情況也會直接進(jìn)入老年代:

        大對象

        大對象指需要大量連續(xù)內(nèi)存空間的對象,這部分對象不管是不是“朝生夕死”,都會直接進(jìn)到老年代。這樣做主要是為了避免在 Eden 區(qū)及 2 個(gè) Survivor 區(qū)之間發(fā)生大量的內(nèi)存復(fù)制。當(dāng)你的系統(tǒng)有非常多“朝生夕死”的大對象時(shí),需要注意。

        長期存活對象

        虛擬機(jī)給每個(gè)對象定義了一個(gè)對象年齡 Age 計(jì)數(shù)器。正常情況下對象會不斷的在 Survivor 的 From 區(qū)與 To 區(qū)之間移動,對象在 Survivor 區(qū)中每經(jīng)歷一次 Minor GC,年齡就增加 1 歲。當(dāng)年齡增加到 15 歲時(shí),這時(shí)候就會被轉(zhuǎn)移到老年代。

        動態(tài)對象年齡

        虛擬機(jī)并不重視要求對象年齡必須到 15 歲,才會放入老年區(qū),如果 Survivor 空間中相同年齡所有對象大小的總合大于 Survivor 空間的一半,年齡大于等于該年齡的對象就可以直接進(jìn)去老年區(qū)。

        空間分配擔(dān)保

        在發(fā)生 Minor GC 之前,虛擬機(jī)會先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象總空間。

        如果條件成立的話,Minor GC 是可以確保安全的。

        如果不成立,則虛擬機(jī)會查看 HandlePromotionFailure 設(shè)置是否擔(dān)保失敗,如果允許,那么會繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對象的平均大小。

        如果大于,嘗試進(jìn)行一次 Minor GC。

        如果小于或者 HandlePromotionFailure 不允許,則進(jìn)行一次 Full GC。


        End

        看累了吧,學(xué)到了吧,那就關(guān)注一下唄!

        學(xué)到就是賺到,歡迎在看、點(diǎn)贊、轉(zhuǎn)發(fā),您的認(rèn)可是我原創(chuàng)的動力!

        瀏覽 51
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            acca少女sdanvi的最新消息 | 男生操女生的视频. | 色偷偷色噜噜狠狠成人免费视频 | 日韩一区二区三区无码电影 | 亚洲午夜精品久久久 | 日本在线不卡视频 | 操逼小视频黄色一级 | 波多野结衣一区二区三区四区 | 国产日韩精品欧美一区二区三区 | 污污污污污网站 |