国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

jvm垃圾回收三部曲

wanghui / 3300人閱讀

摘要:強(qiáng)引用中最常見(jiàn)的引用,引用計(jì)數(shù)算法的就是典型的強(qiáng)引用,只要強(qiáng)引用還存在,垃圾收集器永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象。

概述

早在半個(gè)世紀(jì)以前,第一個(gè)使用了內(nèi)存動(dòng)態(tài)分配和垃圾收集技術(shù)的語(yǔ)言Lisp就已經(jīng)誕生了,從那時(shí),人們就在思考關(guān)于gc需要完成的三件事請(qǐng):

哪些內(nèi)存需要回收

什么時(shí)候回收

如何回收

直到今天已經(jīng)有越來(lái)越多的語(yǔ)言開(kāi)始內(nèi)置內(nèi)存動(dòng)態(tài)分配和垃圾收集技術(shù)。經(jīng)過(guò)長(zhǎng)時(shí)間的發(fā)展,這些技術(shù)已經(jīng)相當(dāng)成熟,一切都看起來(lái)已經(jīng)進(jìn)入“自動(dòng)化”,那為什么我們還要去學(xué)習(xí)gc和內(nèi)存分配呢?當(dāng)我們需要去拍查內(nèi)存溢出和內(nèi)存泄露時(shí),當(dāng)垃圾收集成為系統(tǒng)達(dá)到高并發(fā)量的瓶頸時(shí),我們就需要揭開(kāi)這些“自動(dòng)化”技術(shù)的內(nèi)幕,去實(shí)施必要的監(jiān)控和調(diào)節(jié)。

上篇講到j(luò)vm運(yùn)行時(shí)內(nèi)存區(qū)域主要包括這么幾部分區(qū)域:

程序計(jì)數(shù)器

虛擬機(jī)棧

本地方法棧

堆內(nèi)存

方法區(qū)

其中程序計(jì)數(shù)器,虛擬機(jī)棧和本地方法棧都會(huì)隨著線程而生,隨著線程而滅,正常情況下不會(huì)出現(xiàn)內(nèi)存溢出和泄露的問(wèn)題,無(wú)需對(duì)這塊區(qū)域多做關(guān)心。后文討論的內(nèi)存區(qū)域都是堆內(nèi)存或方法區(qū)。

對(duì)象已死嗎

在堆里幾乎放著java世界里所有的對(duì)象實(shí)例,垃圾收集器對(duì)齊進(jìn)行回收的第一件事就是要判斷需要回收哪些對(duì)象,哪些對(duì)象已死(也就是哪些對(duì)象已經(jīng)不可能用到了,但還是存在于堆內(nèi)存當(dāng)中)。

引用計(jì)數(shù)算法
引用計(jì)數(shù)算法定義很簡(jiǎn)單,給對(duì)象添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí),就進(jìn)行+1,每當(dāng)有一個(gè)地方失效時(shí),就進(jìn)行-1。任何時(shí)刻當(dāng)計(jì)數(shù)器為0時(shí)的對(duì)象都是不可能再被使用的。這種算法實(shí)現(xiàn)簡(jiǎn)單,判定效率也很高,是一個(gè)很不錯(cuò)的算法,也有一些非常著名的案例,例如微軟公司的COM技術(shù),Python語(yǔ)言和在游戲腳本領(lǐng)域應(yīng)用非常廣泛的Squirrel都使用了引用計(jì)數(shù)算法來(lái)管理,但至少目前為主流的java商用虛擬機(jī)沒(méi)有選用其來(lái)管理內(nèi)存,其根本原因是它很難解決對(duì)象之間互相引用的問(wèn)題,看下面一個(gè)小例子:

    /**
     * vmargs:-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -Xms20m -Xmx20m
     */
    public class Main {
    
        static class ReferenceCount {
            private Object object;
            //大對(duì)象,用來(lái)感知gc是否被回收
            private byte[] bigObject = new byte[1024 * 1024 * 2];
        }
    
        public static void main(String[] args) {
    
            ReferenceCount referenceCountA = new ReferenceCount();//對(duì)象A被引用1次
            ReferenceCount referenceCountB = new ReferenceCount();//對(duì)象B被引用1次
    
            referenceCountA.object=referenceCountB;//對(duì)象B被引用兩次
            referenceCountB.object=referenceCountA;//對(duì)象A被引用兩次
            //引用失效-1,對(duì)象A和對(duì)象B均被引用1次
            referenceCountA=null;
            referenceCountB=null;
            //執(zhí)行fullgc,查看堆內(nèi)存使用量來(lái)判斷是否回收
            System.gc();
    
        }
    }

其gc日志為:

或許大家還看不懂gc日志,但沒(méi)關(guān)心,我們只需要關(guān)注紅色區(qū)域,進(jìn)行System.gc后堆內(nèi)存區(qū)域只用了463k,很明顯對(duì)象AB已經(jīng)被回收了。

可達(dá)性分析算法
在主流的商用語(yǔ)言中(java、C#甚至Lisp當(dāng)中)的主流實(shí)現(xiàn)中,都是采用可達(dá)性分析算法來(lái)判定對(duì)象是否存活的。這個(gè)算法的基本思路就是通過(guò)一系列的“GC Roots”的對(duì)象作為起點(diǎn),從這些起點(diǎn)開(kāi)始向下搜索所走過(guò)的路徑稱為引用鏈,當(dāng)一個(gè)對(duì)象到“GC Roots”沒(méi)有任何引用鏈相連,就證明這個(gè)對(duì)象是不可用的。如下圖所示:

在java語(yǔ)言中,可以作為GC Roots的對(duì)象包括下面4種:
1.虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象
2.方法區(qū)中類靜態(tài)屬性引用的對(duì)象
3.方法區(qū)中常量引用的對(duì)象
4.本地方法中JNI(jdk里的native方法)引用的對(duì)象

java的四種引用

無(wú)論通過(guò)哪種算法去判斷對(duì)象是否存活都與引用相關(guān)。在java中,分為強(qiáng)引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)4種,其引用關(guān)系依次降低。

強(qiáng)引用:java中最常見(jiàn)的引用,引用計(jì)數(shù)算法的ReferenceCount referenceCountA = new ReferenceCount()就是典型的強(qiáng)引用,只要強(qiáng)引用還存在,垃圾收集器永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象。
軟引用:用來(lái)描述一些還有用但并不是必須存在的對(duì)象(可以與緩存的功能作類比),對(duì)于軟銀用的對(duì)象,在系統(tǒng)即將發(fā)生內(nèi)存溢出異常之前,將會(huì)把這些對(duì)象列入到第二次回收范圍中進(jìn)行回收,如果回收之后還是沒(méi)有足夠的內(nèi)存,將拋出內(nèi)存溢出異常。jdk提供了SoftRerence來(lái)實(shí)現(xiàn)軟引用
弱引用:它的作用和軟引用類似,區(qū)別在于引用關(guān)系更弱。只能存活到下次gc發(fā)生之前。當(dāng)gc時(shí),無(wú)論當(dāng)前內(nèi)存是否足夠都會(huì)回收掉弱引用的關(guān)聯(lián)的對(duì)象。jdk提供了WeakReference引用。
虛引用:它是最弱的一種引用關(guān)系,為一個(gè)對(duì)象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是這個(gè)對(duì)象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知。jdk提供了PhantomReference來(lái)實(shí)現(xiàn)虛引用。

回收方法區(qū)
在hotspot中,大家更原因?qū)⑵浞Q為永久代(jdk1.8廢除永久代,metaspace元空間出現(xiàn),咱不討論)。永久代主要回收兩部分內(nèi)容:廢棄常量和無(wú)用的類。以一個(gè)字符串“abc”已經(jīng)進(jìn)入了常量池中,但是系統(tǒng)中沒(méi)有一個(gè)String對(duì)象指向abc的,也沒(méi)有其他地方引用了這個(gè)字面量,當(dāng)發(fā)生垃圾回收時(shí),并且必要的話,這個(gè)常量就會(huì)被系統(tǒng)清理出常量池。常量池中的其他類(接口)、方法、字段的符號(hào)引用也與此類似。判定一個(gè)常量是否存活比較簡(jiǎn)單,而要判定一個(gè)類是否是無(wú)用的類的條件就要苛刻很多。必須得滿足以下三個(gè)條件:1.該類的所有實(shí)例已經(jīng)被回收2.加載該類的ClassLoader已經(jīng)被回收3.該類對(duì)應(yīng)的Class對(duì)象沒(méi)有在其他任何地方被引用,無(wú)法再任何地方通過(guò)反射訪問(wèn)該類的方法。只有滿足以上三個(gè)條件,這個(gè)類才有可能被回收。是否對(duì)類進(jìn)行回收,hotspot虛擬機(jī)提供了-Xnoclassgc參數(shù)進(jìn)行控制。

垃圾收集算法

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

標(biāo)記清除算法(Mark-sweep)是最基礎(chǔ)的算法,其過(guò)程如同名字一樣分為標(biāo)記和清除兩個(gè)階段:首先標(biāo)記出所有需要回收的對(duì)象,標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象。之所以說(shuō)它是最基礎(chǔ)的算法是因?yàn)楹罄m(xù)的收集算法都是在它的基礎(chǔ)上對(duì)其不足進(jìn)行改進(jìn)而得到的(但同時(shí)也會(huì)暴露其他問(wèn)題,沒(méi)有最合適,只有更合適)。它的不足主要有兩個(gè),一個(gè)是效率問(wèn)題,標(biāo)記和清除的效率都不高。另一個(gè)是空間問(wèn)題,會(huì)產(chǎn)生大量的內(nèi)存碎片,碎片太多可能會(huì)導(dǎo)致以后分配大對(duì)象無(wú)法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動(dòng)作。執(zhí)行過(guò)程如下圖所示:

復(fù)制算法
為了解決標(biāo)記清除算法的效率問(wèn)題,復(fù)制算法出現(xiàn)了。他將可用內(nèi)存按大小分為大小相等的兩份,每次只使用其中的一份,當(dāng)其中一份內(nèi)存使用完了,就將還存活著的對(duì)象復(fù)制到另外一塊上面,然后再將已使用的那份內(nèi)存空間一次清理掉。這種算法只對(duì)整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收,內(nèi)存分配時(shí)也不再需要考慮內(nèi)存碎片的問(wèn)題,只要每次移動(dòng)堆頂指針,按順序分配內(nèi)存即可,實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行高效。缺點(diǎn)是將可用內(nèi)存縮小了一半,代價(jià)太高。執(zhí)行過(guò)程如下所示:

目前的商業(yè)虛擬機(jī)都采用復(fù)制算法來(lái)回收新生代。新生代中的對(duì)象98%都是朝生夕死的,所以我們不需要嚴(yán)格的按照1:1的比例來(lái)劃分內(nèi)存空間。目前的商用虛擬機(jī)都將新生代內(nèi)存劃分為Eden和兩塊Survivor空間,其比例默認(rèn)為8:1:1,每次只使用Eden和其中一塊Survivor空間。當(dāng)回收內(nèi)存時(shí),會(huì)將Eden和已經(jīng)使用的Survivor空間中存活的對(duì)象一次性的復(fù)制到另外一塊Survivor空間,復(fù)制完成然后清理Eden區(qū)域和剛才用過(guò)的Survivor空間。每次只有10%的內(nèi)存不可用算是對(duì)復(fù)制算法的一個(gè)優(yōu)化,是可以被接受的。另外前面有提到,一般場(chǎng)景98%的對(duì)象都是朝生夕死,但是我們沒(méi)有辦法保證Eden和其中一塊使用的Survivor空間存活的對(duì)象一定比另外一塊未使用的Survivor空間小,如果未使用的Survivor空間不夠用時(shí),需要依賴?yán)夏甏M(jìn)行分配擔(dān)保(Handle Promotion)。

標(biāo)記-整理算法
復(fù)制收集算法尤其適合新生代,因?yàn)樾律鷮?duì)象一般情境下都是朝生夕死的。但是如果在對(duì)象存活率較高甚至極端情況下達(dá)到100%的存活率,就要進(jìn)行較多的復(fù)制操作,效率將會(huì)變得極其低下,又因?yàn)樾枰~外的空間進(jìn)行擔(dān)保,所以老年代不會(huì)選用復(fù)制算法(老年代的對(duì)象一般都活的比較久)。
根據(jù)老年代對(duì)象的特點(diǎn),就提出了標(biāo)記整理算法。第一步仍是標(biāo)記,但后續(xù)步驟不再是清除而是整理,讓所有存活的對(duì)象都向一端移動(dòng),然后直接清理掉端邊界以外的內(nèi)存。執(zhí)行過(guò)程如下圖所示:

分代收集算法
前面有提到新生代和老年代,其實(shí)是根據(jù)對(duì)象存活周期的不同來(lái)劃分的。新生代中,每次垃圾收集的對(duì)象都會(huì)有大批死去,只有少量存活,那就用復(fù)制算法,只需要少量存活對(duì)象的復(fù)制成本就可以完成收集。而老年代對(duì)象存活率高,又沒(méi)有額外空間對(duì)它進(jìn)行分配擔(dān)保,就必須使用標(biāo)記整理和表表情清除算法來(lái)進(jìn)行回收,這就是所謂的分代收集算法。

枚舉根節(jié)點(diǎn)

從可達(dá)性分析中從GC Roots節(jié)點(diǎn)找引用鏈這個(gè)操作為例,可以作為GC Roots的節(jié)點(diǎn)主要為常量、靜態(tài)、和棧幀中的本地變量表,現(xiàn)在很多程序僅僅方法區(qū)就有數(shù)百兆,如果逐個(gè)檢查里面的引用,那么必然會(huì)消耗很多的時(shí)間。另外,可達(dá)性分析對(duì)執(zhí)行時(shí)間的敏感還體現(xiàn)在GC停頓上,因?yàn)檫@項(xiàng)分析工作必須在一個(gè)確保一致性的快照中進(jìn)行。這個(gè)一致性的意思是整個(gè)分析期間這個(gè)程序看起來(lái)就像被凍結(jié)在某個(gè)時(shí)間點(diǎn)一樣,不可以出現(xiàn)分析過(guò)程中對(duì)象引用關(guān)系還在不斷變化的情況,這點(diǎn)如果不滿足的話結(jié)果準(zhǔn)確性就無(wú)從談起了。這點(diǎn)是導(dǎo)致gc進(jìn)行時(shí)程序必須停頓所有java工作線程的一個(gè)重要原因,Sun公司將其稱為STW(Stop the World)。聽(tīng)起來(lái)很酷,但這很有可能是造成接口超時(shí)等其他問(wèn)題的罪魁禍?zhǔn)住?/p> 垃圾收集器

如果說(shuō)收集算法是垃圾回收的方法論,那么垃圾收集器就是垃圾回收的具體實(shí)現(xiàn)。虛擬機(jī)規(guī)范并沒(méi)有規(guī)定虛擬機(jī)如何去實(shí)現(xiàn),因此不同廠商不同版本的虛擬機(jī)提供的垃圾收集器都可能會(huì)有很大差別。本文只討論JDK1.7Update14之后的hotspot虛擬機(jī),如下圖所示:

Young generation代表年輕代,Tenured generation代表老年代。總共有7款收集器,他們都負(fù)責(zé)回收自己所在的區(qū)域,收集器之間的實(shí)線代表著這兩款收集器可以合作,一起收集整個(gè)堆內(nèi)存。其中G1是JDK1.7之后才正式被oracle定義為商用虛擬機(jī),G1回收整個(gè)新生代和部分老年代比較特殊,本文暫不討論。

在講解收集器之前,先給大家介紹下32位和64位的jdk。32位系統(tǒng)只可以裝32位jdk,64位系統(tǒng)兩者都可以裝,但推薦安裝64位jdk。在32位的jdk下,虛擬機(jī)的模式是可選的,默認(rèn)為client模式,可以通過(guò)修改配置文件為server模式,但在64位的jdk下,虛擬機(jī)只能為server模式。目前大部分服務(wù)器甚至很多個(gè)人電腦都是64bit,也就是默認(rèn)server模式。

在接下來(lái)介紹的六款收集器中,只有serial和serial old是單線程回收內(nèi)存的收集器。其他都是多條線程回收內(nèi)存的,有的是并行,有的是并發(fā),在介紹這幾款收集器之前,我們先講解下并行和并發(fā)在垃圾回收這個(gè)上衣文語(yǔ)境中所代表的含義:

1.并行:多條垃圾收集線程并行工作,但用戶線程仍在等待狀態(tài)
2.并發(fā):用戶線程與垃圾收集線程同時(shí)執(zhí)行(可能會(huì)交替執(zhí)行),用戶程序在運(yùn)行在一部分cpu上,而垃圾回收運(yùn)行在另一部分cpu上。

serial收集器
serial收集器是一款歷史很悠久的收集器,在jdk1.3之前是新生代的唯一選擇。這是一個(gè)單線程的收集器,它只會(huì)占用一個(gè)cpu啟動(dòng)一個(gè)線程去回收內(nèi)存,但它也是會(huì)導(dǎo)致stw的。下圖為serila和serial old收集器工作的示意圖:

直到今天,它依然是虛擬機(jī)運(yùn)行在client模式下默認(rèn)的新生代收集器,在用戶的桌面場(chǎng)景應(yīng)用中,分配給虛擬機(jī)管理的內(nèi)存一般不會(huì)太大,停頓時(shí)間完全可以控制在一百毫秒以內(nèi),只要不是頻繁發(fā)生,這點(diǎn)停頓是可以接受的。

parnew收集器
parnew收集器就是serial收集器的多線程版本,除了使用多個(gè)線程進(jìn)行垃圾回收之外,其余行為包括serial收集器所有可用的控制參數(shù)、收集算法、stw、對(duì)象分配規(guī)則、回收策略斗魚serial一模一樣。在實(shí)現(xiàn)上,兩種收集器也共用了很多代碼。parnew和serial Old收集器工作的示意圖:

parnew收集器是虛擬機(jī)server模式下默認(rèn)的新生代虛擬機(jī),但是它和serial相比除了是多線程收集外并沒(méi)有其他的特色,其中一個(gè)與性能無(wú)關(guān)但很重要的原因,目前除了serial收集器,它是唯一一個(gè)可以cms共同工作的一個(gè)收集器。在jdk1.5,hotspot推出了一款劃時(shí)代意義的垃圾收集器-----cms收集器,這款收集器是真正意義上的第一款并發(fā)收集器,他第一次實(shí)現(xiàn)了讓用戶線程和垃圾收集線程基本上同時(shí)工作(但stw還是存在的,稍后會(huì)講解cms收集器的內(nèi)容)。不幸的是,cms作為老年代的收集器,卻只能和serial和parnew收集器共同工作,parnew收集器也是使用-XX:+UseConcMarkSweepGC參數(shù)后的默認(rèn)新生代收集器。

parallel scavenge收集器
parallel scavenge收集器也是一個(gè)新生代的收集器和parnew大致一樣,但它的關(guān)注點(diǎn)與其他收集器不同。其他收集器都是盡可能地縮短垃圾收集時(shí)用戶線程的停頓時(shí)間,而parallel scavenge收集器的目的是為了達(dá)到一個(gè)可控制的吞吐量(Throughput)。吞吐量就是cpu用于運(yùn)行用戶代碼的時(shí)間與cpu工作時(shí)間的比值,即吞吐量的計(jì)算應(yīng)該為:吞吐量=用戶線程的工作時(shí)間/(用戶線程的工作時(shí)間和垃圾收集的時(shí)間),虛擬機(jī)總共運(yùn)行100分鐘,垃圾收集器運(yùn)行2分鐘,那吞吐量就是98%。停頓時(shí)間越短,就越適合需要與用戶交互的系統(tǒng)程序,可以良好的提升用戶體驗(yàn),而吞吐量越高說(shuō)明cpu的利用效率越高,可以盡快完成的程序的運(yùn)算任務(wù),主要適合后臺(tái)運(yùn)算而不需要太多的交互任務(wù)。
parallel scavenge收集器提供了兩個(gè)參數(shù)用于控制吞吐量,分別是控制最大垃圾收集停頓時(shí)間的-XX:MaxGCPauseMills和直接設(shè)置吞吐量大小的-XX:GCTimeRatio參數(shù)。XX:MaxGCPauseMills允許的值是一個(gè)大于0的毫秒數(shù),收集器盡可能保證內(nèi)存回收花費(fèi)的時(shí)間不超過(guò)此值,但并不是將此參數(shù)設(shè)的越小系統(tǒng)的垃圾收集速度就越快,它是犧牲了新生代空間和吞吐量來(lái)?yè)Q取的:收集500MB的新生代肯定比收集1GB的新生代快,但換來(lái)的代價(jià)是 ygc會(huì)更頻繁一些。原來(lái)10秒一次ygc,一次停頓100ms,現(xiàn)在5秒一次ygc,一次停頓60ms。停頓時(shí)間在下降,但吞吐量也降下來(lái)了。GCTimeRatio參數(shù)的值應(yīng)當(dāng)是一個(gè)大于0小于100的整數(shù),也就是垃圾收集時(shí)間占總時(shí)間的比率,如果把參數(shù)設(shè)置為9,那允許的最大gc時(shí)間就占總時(shí)間的10%,計(jì)算方法是這樣子的(1/(1+9))。
parallel scavenge收集器還提供了一個(gè)參數(shù)-XX:+UserAdaptiveSizePolicy,這是一個(gè)開(kāi)關(guān)參數(shù),當(dāng)這個(gè)參數(shù)打開(kāi)后,就不需要手工指定新生代的大小、Eden與Survivor的比例、晉升老年代對(duì)象的大小等參數(shù)了,虛擬機(jī)會(huì)根據(jù)運(yùn)行情況尋找最合適的配比。如果對(duì)于收集器運(yùn)作不太了解,可以只配置-Xmx設(shè)置最大堆,配置MaxGCPauseMills設(shè)置最大停頓時(shí)間或GCTimeRatios來(lái)設(shè)置吞吐量給虛擬機(jī)來(lái)建立一個(gè)優(yōu)化目標(biāo),具體的細(xì)節(jié)參數(shù)交給parallel scavenge收集器來(lái)自動(dòng)調(diào)配。

serial old 收集器
serial Old是serial收集起的老年代版本,同樣的他也是一個(gè)單線程收集器,使用標(biāo)記整理算法(注意新生代使用的都是復(fù)制算法,前面有提到),這個(gè)收集器的主要意義也是給client模式下虛擬機(jī)來(lái)使用。如果在server模式下,它還有兩大用途:一種用途是JDK1.5以及之前的版本中配合parallel scavenge使用的,另一種用途就是作為cms收集器的備用方案,在這款收集器發(fā)生Concurrent Mode Failure時(shí)切換為Serial old收集器

parallel old收集器
parallel old收集器是parallel scavenge收集器的老年代版本,使用多線程和標(biāo)記整理算法,在jdk1.6才開(kāi)始提供的。所以在此之前新生代的parallel scavenge一直處于十分尷尬的狀態(tài),如果新生代使用了parallel scavenge收集器,老年代只能與serial old配合(parallel scavenge收集器無(wú)法與cms工作)。由于serila old收集器在服務(wù)器應(yīng)用性能上的拖累,使用parallel scavenge收集器也未必能夠獲得吞吐量最大化的效果,其原因是因?yàn)閟erial old是單線程的無(wú)法充分利用服務(wù)器多cpu的處理能力,在老年代很大且硬件比較高級(jí)的環(huán)境中,這種組合的吞吐量還不一定有parnew+cms的組合給力。直到parallel old收集器出現(xiàn)后,吞吐量?jī)?yōu)先收集器才有了比較名副其實(shí)的組合,在注重吞吐量和cpu資源非常敏感的情況下,都可以優(yōu)先考慮parallel old+parallel scavenge收集器,其工作過(guò)程如下所示:

cms收集器
cms(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。目前很大一部分的java應(yīng)用集中在互聯(lián)網(wǎng)站或者B/S的系統(tǒng)服務(wù)端上,這類應(yīng)用非常重視響應(yīng)速度,希望停頓時(shí)間最短,cms收集器就非常符合這類需求。
cms是一款基于標(biāo)記清除算法的收集器,在jdk1.5中提出,也是hotspot第一款并發(fā)收集器。它的運(yùn)作過(guò)程相對(duì)于前面幾款收集器復(fù)雜一些,正題分為4個(gè)步驟:

7. 初始標(biāo)記(CMS initial mark):初始標(biāo)記需要stw,它僅僅只是標(biāo)記下GC Roots能直接關(guān)聯(lián)到的對(duì)象,速度很快。

7. 并發(fā)標(biāo)記(CMS concurrent mark):不需要stw,這個(gè)階段是根據(jù)初始標(biāo)記得到的存活對(duì)象進(jìn)行遞歸標(biāo)記可達(dá)的對(duì)象。
8. 重新標(biāo)記(CMS remark):并發(fā)標(biāo)記這個(gè)階段是并發(fā)執(zhí)行的(用戶線程也在工作),可能發(fā)生對(duì)象晉升到老年代或者大對(duì)象直接分配老年代或者新老年代對(duì)象的引用關(guān)系被更新等等,對(duì)于這些變動(dòng)的引用關(guān)系需要重新標(biāo)記更正,并且發(fā)生stw,會(huì)比初始標(biāo)記時(shí)間要長(zhǎng),但遠(yuǎn)比并發(fā)標(biāo)記時(shí)間短。
9. 并發(fā)清除(CMS concurrent sweep):并發(fā)清除就很簡(jiǎn)單了,之前已經(jīng)整理出存活對(duì)象,直接清除就是了。

由于最耗時(shí)間的并發(fā)標(biāo)記和并發(fā)清除都可以和用戶線程一起工作,所以總體上來(lái)說(shuō)cms收集器的內(nèi)存回收過(guò)程是與用戶線程一起兵法執(zhí)行的。cms的工作流程如下圖所示:

cms是一款優(yōu)秀的收集器,并發(fā)收集和低停頓都是他的特點(diǎn),但它有3個(gè)明顯的缺點(diǎn):

cms收集器對(duì)cpu資源非常敏感,因?yàn)槭遣l(fā)的會(huì)去搶奪cpu資源,造成應(yīng)用程序突然變慢,總吞吐量降低的情況。

cms基于標(biāo)記清除算法實(shí)現(xiàn)的,無(wú)法清除浮動(dòng)垃圾,可能出現(xiàn)Concurrent Mode Failure失敗導(dǎo)致另一次Full GC的產(chǎn)生。由于cms的并發(fā)清理階段用戶線程還在運(yùn)行著,必然會(huì)有新的垃圾不斷產(chǎn)生,這部分垃圾出現(xiàn)在標(biāo)記之后,cms無(wú)法在當(dāng)次收集過(guò)程回收它,只能留到下一次回收,因此cms不能像其他年老代收集器一樣等到年老代幾乎被填滿了再進(jìn)行收集,需要預(yù)留一部分空間提供并發(fā)清理時(shí)的程序運(yùn)作使用。在jdk1.5中cms收集器當(dāng)老年代使用了68%的空間后就會(huì)被激活,在jdk1.5之后,已經(jīng)將閾值提升至92%。如果Cms預(yù)留內(nèi)存不夠用,將會(huì)發(fā)生Concurrent Mode Failure,這時(shí)將會(huì)啟動(dòng)備用方案,臨時(shí)啟動(dòng)serial old來(lái)進(jìn)行老年代的手機(jī),這樣停頓時(shí)間就很長(zhǎng)了。這個(gè)閾值可以通過(guò)參數(shù)-XX:CMSInitiatingOccupancyFraction來(lái)設(shè)置,設(shè)置的太高,老年代增長(zhǎng)的又比較快,就會(huì)導(dǎo)致大量Concurrent Mode failure出現(xiàn),性能反而降低。

cms是基于一款標(biāo)記清楚算法實(shí)現(xiàn)的收集器,這種算法會(huì)導(dǎo)致大量空間碎片產(chǎn)生。這將會(huì)給分配大對(duì)象帶來(lái)麻煩,往往老年代空間還很多,但是無(wú)法找到足夠大的連續(xù)空間來(lái)分配當(dāng)前對(duì)象,不得不觸發(fā)一次Full GC。為了解決這個(gè)問(wèn)題,CMS收集器提供了一個(gè)開(kāi)關(guān)參數(shù):-XX:+UseCMSCompactAtFullCollection(默認(rèn)開(kāi)啟),用于cms收集器頂不住要進(jìn)行fullGC 時(shí)開(kāi)啟內(nèi)存碎片的合并整理過(guò)程,這個(gè)過(guò)程是無(wú)法并發(fā)的,空間碎片問(wèn)題沒(méi)有了,但停頓時(shí)間不得不變長(zhǎng)。cms還提供了一個(gè)參數(shù)-XX:CMSFullGCsBeforeCompaction來(lái)設(shè)置執(zhí)行多少次不壓縮的Full GC后跟著來(lái)一次壓縮整理。

總結(jié)

本篇博客主要講解了垃圾回收算法,內(nèi)存區(qū)域的一些細(xì)節(jié)和收集器大致的工作流程。通過(guò)分析比較各個(gè)收集器,我們發(fā)現(xiàn)沒(méi)有最好的收集器組合,更沒(méi)有萬(wàn)能的收集器組合。我們只能通過(guò)場(chǎng)景分析來(lái)定最合適的收集器。

下節(jié)預(yù)告

1.gc日志的閱讀
2.內(nèi)存分配和回收策略
3.虛擬機(jī)提供的性能監(jiān)控工具

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/68989.html

相關(guān)文章

  • 內(nèi)存泄露從入門到精通部曲之基礎(chǔ)知識(shí)篇

    摘要:騰訊特約作者姚潮生首先以一個(gè)內(nèi)存泄露實(shí)例來(lái)開(kāi)始本節(jié)基礎(chǔ)概念的內(nèi)容。堆內(nèi)存用于存放所有由創(chuàng)建的對(duì)象內(nèi)容包括該對(duì)象其中的所有成員變量和數(shù)組。回到我們的問(wèn)題,為什么內(nèi)存會(huì)泄露堆內(nèi)存中的長(zhǎng)生命周期的對(duì)象持有短生命周期對(duì)象的強(qiáng)軟引用,盡管 騰訊Bugly特約作者: 姚潮生 首先以一個(gè)內(nèi)存泄露實(shí)例來(lái)開(kāi)始本節(jié)基礎(chǔ)概念的內(nèi)容。 實(shí)例1:?jiǎn)卫龑?dǎo)致內(nèi)存對(duì)象無(wú)法釋放而泄露 showImg(http://i....

    nicercode 評(píng)論0 收藏0
  • 內(nèi)存泄露從入門到精通部曲之排查方法篇

    摘要:對(duì)比操作前后的來(lái)定位內(nèi)存泄露的根因所在。手機(jī)管家內(nèi)存泄露每日監(jiān)控方案目前手機(jī)管家的內(nèi)存泄露每日監(jiān)控會(huì)自動(dòng)運(yùn)行并輸出是否存在疑似泄露的報(bào)告郵件,不論泄露對(duì)象的大小。 騰訊Bugly特約作者: 姚潮生 最原始的內(nèi)存泄露測(cè)試 重復(fù)多次操作關(guān)鍵的可疑的路徑,從內(nèi)存監(jiān)控工具中觀察內(nèi)存曲線,是否存在不斷上升的趨勢(shì)且不會(huì)在程序返回時(shí)明顯回落。這種方式可以發(fā)現(xiàn)最基本,也是最明顯的內(nèi)存泄露問(wèn)題,對(duì)用戶價(jià)...

    李世贊 評(píng)論0 收藏0
  • 內(nèi)存泄露從入門到精通部曲之排查方法篇

    摘要:對(duì)比操作前后的來(lái)定位內(nèi)存泄露的根因所在。手機(jī)管家內(nèi)存泄露每日監(jiān)控方案目前手機(jī)管家的內(nèi)存泄露每日監(jiān)控會(huì)自動(dòng)運(yùn)行并輸出是否存在疑似泄露的報(bào)告郵件,不論泄露對(duì)象的大小。 騰訊Bugly特約作者: 姚潮生 最原始的內(nèi)存泄露測(cè)試 重復(fù)多次操作關(guān)鍵的可疑的路徑,從內(nèi)存監(jiān)控工具中觀察內(nèi)存曲線,是否存在不斷上升的趨勢(shì)且不會(huì)在程序返回時(shí)明顯回落。這種方式可以發(fā)現(xiàn)最基本,也是最明顯的內(nèi)存泄露問(wèn)題,對(duì)用戶價(jià)...

    sutaking 評(píng)論0 收藏0
  • 細(xì)述 Java垃圾回收機(jī)制→How Java Garbage Collection Works?

    摘要:當(dāng)一個(gè)實(shí)例被創(chuàng)建的時(shí)候,它最初被存放在堆內(nèi)存空間的年輕代的區(qū)中。老年代或者永久代是堆內(nèi)存的第二個(gè)邏輯部分。在垃圾回收過(guò)程中掃描屬于部分的堆內(nèi)存。一旦實(shí)例從堆內(nèi)存中刪除了,它們?cè)瓉?lái)的位置將空出來(lái)給以后分配實(shí)例使用。 本文非原創(chuàng),翻譯自How Java Garbage Collection Works?在Java中為對(duì)象分配和釋放內(nèi)存空間都是由垃圾回收線程自動(dòng)執(zhí)行完成的。和C語(yǔ)言不一樣的是...

    cc17 評(píng)論0 收藏0
  • 細(xì)述 Java垃圾回收機(jī)制→Java Garbage Collection Introduction

    摘要:在架構(gòu)中,堆內(nèi)存和垃圾回收器這兩個(gè)部分和垃圾回收相關(guān)。堆內(nèi)存在的內(nèi)存模型中,最重要的是要了解堆內(nèi)存的概念。在垃圾回收的過(guò)程中,這些對(duì)象將被從堆內(nèi)存中清除,同時(shí)它們的空間也就被回收了。 本文非原創(chuàng),翻譯自Java Garbage Collection introduction在Java中為對(duì)象分配和釋放內(nèi)存空間都是由垃圾回收線程自動(dòng)執(zhí)行完成的。和C語(yǔ)言不一樣的是Java程序員不需要手動(dòng)寫...

    codergarden 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

wanghui

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<