摘要:強(qiáng)引用中最常見的引用,引用計(jì)數(shù)算法的就是典型的強(qiáng)引用,只要強(qiáng)引用還存在,垃圾收集器永遠(yuǎn)不會回收掉被引用的對象。
概述
早在半個世紀(jì)以前,第一個使用了內(nèi)存動態(tài)分配和垃圾收集技術(shù)的語言Lisp就已經(jīng)誕生了,從那時,人們就在思考關(guān)于gc需要完成的三件事請:
哪些內(nèi)存需要回收
什么時候回收
如何回收
直到今天已經(jīng)有越來越多的語言開始內(nèi)置內(nèi)存動態(tài)分配和垃圾收集技術(shù)。經(jīng)過長時間的發(fā)展,這些技術(shù)已經(jīng)相當(dāng)成熟,一切都看起來已經(jīng)進(jìn)入“自動化”,那為什么我們還要去學(xué)習(xí)gc和內(nèi)存分配呢?當(dāng)我們需要去拍查內(nèi)存溢出和內(nèi)存泄露時,當(dāng)垃圾收集成為系統(tǒng)達(dá)到高并發(fā)量的瓶頸時,我們就需要揭開這些“自動化”技術(shù)的內(nèi)幕,去實(shí)施必要的監(jiān)控和調(diào)節(jié)。
上篇講到j(luò)vm運(yùn)行時內(nèi)存區(qū)域主要包括這么幾部分區(qū)域:
程序計(jì)數(shù)器
虛擬機(jī)棧
本地方法棧
堆內(nèi)存
方法區(qū)
其中程序計(jì)數(shù)器,虛擬機(jī)棧和本地方法棧都會隨著線程而生,隨著線程而滅,正常情況下不會出現(xiàn)內(nèi)存溢出和泄露的問題,無需對這塊區(qū)域多做關(guān)心。后文討論的內(nèi)存區(qū)域都是堆內(nèi)存或方法區(qū)。
對象已死嗎在堆里幾乎放著java世界里所有的對象實(shí)例,垃圾收集器對齊進(jìn)行回收的第一件事就是要判斷需要回收哪些對象,哪些對象已死(也就是哪些對象已經(jīng)不可能用到了,但還是存在于堆內(nèi)存當(dāng)中)。
引用計(jì)數(shù)算法
引用計(jì)數(shù)算法定義很簡單,給對象添加一個引用計(jì)數(shù)器,每當(dāng)有一個地方引用它時,就進(jìn)行+1,每當(dāng)有一個地方失效時,就進(jìn)行-1。任何時刻當(dāng)計(jì)數(shù)器為0時的對象都是不可能再被使用的。這種算法實(shí)現(xiàn)簡單,判定效率也很高,是一個很不錯的算法,也有一些非常著名的案例,例如微軟公司的COM技術(shù),Python語言和在游戲腳本領(lǐng)域應(yīng)用非常廣泛的Squirrel都使用了引用計(jì)數(shù)算法來管理,但至少目前為主流的java商用虛擬機(jī)沒有選用其來管理內(nèi)存,其根本原因是它很難解決對象之間互相引用的問題,看下面一個小例子:
/** * vmargs:-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -Xms20m -Xmx20m */ public class Main { static class ReferenceCount { private Object object; //大對象,用來感知gc是否被回收 private byte[] bigObject = new byte[1024 * 1024 * 2]; } public static void main(String[] args) { ReferenceCount referenceCountA = new ReferenceCount();//對象A被引用1次 ReferenceCount referenceCountB = new ReferenceCount();//對象B被引用1次 referenceCountA.object=referenceCountB;//對象B被引用兩次 referenceCountB.object=referenceCountA;//對象A被引用兩次 //引用失效-1,對象A和對象B均被引用1次 referenceCountA=null; referenceCountB=null; //執(zhí)行fullgc,查看堆內(nèi)存使用量來判斷是否回收 System.gc(); } }
其gc日志為:
或許大家還看不懂gc日志,但沒關(guān)心,我們只需要關(guān)注紅色區(qū)域,進(jìn)行System.gc后堆內(nèi)存區(qū)域只用了463k,很明顯對象AB已經(jīng)被回收了。
可達(dá)性分析算法
在主流的商用語言中(java、C#甚至Lisp當(dāng)中)的主流實(shí)現(xiàn)中,都是采用可達(dá)性分析算法來判定對象是否存活的。這個算法的基本思路就是通過一系列的“GC Roots”的對象作為起點(diǎn),從這些起點(diǎn)開始向下搜索所走過的路徑稱為引用鏈,當(dāng)一個對象到“GC Roots”沒有任何引用鏈相連,就證明這個對象是不可用的。如下圖所示:
在java語言中,可以作為GC Roots的對象包括下面4種:
1.虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象
2.方法區(qū)中類靜態(tài)屬性引用的對象
3.方法區(qū)中常量引用的對象
4.本地方法中JNI(jdk里的native方法)引用的對象
java的四種引用
無論通過哪種算法去判斷對象是否存活都與引用相關(guān)。在java中,分為強(qiáng)引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)4種,其引用關(guān)系依次降低。
強(qiáng)引用:java中最常見的引用,引用計(jì)數(shù)算法的ReferenceCount referenceCountA = new ReferenceCount()就是典型的強(qiáng)引用,只要強(qiáng)引用還存在,垃圾收集器永遠(yuǎn)不會回收掉被引用的對象。
軟引用:用來描述一些還有用但并不是必須存在的對象(可以與緩存的功能作類比),對于軟銀用的對象,在系統(tǒng)即將發(fā)生內(nèi)存溢出異常之前,將會把這些對象列入到第二次回收范圍中進(jìn)行回收,如果回收之后還是沒有足夠的內(nèi)存,將拋出內(nèi)存溢出異常。jdk提供了SoftRerence來實(shí)現(xiàn)軟引用
弱引用:它的作用和軟引用類似,區(qū)別在于引用關(guān)系更弱。只能存活到下次gc發(fā)生之前。當(dāng)gc時,無論當(dāng)前內(nèi)存是否足夠都會回收掉弱引用的關(guān)聯(lián)的對象。jdk提供了WeakReference引用。
虛引用:它是最弱的一種引用關(guān)系,為一個對象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是這個對象被收集器回收時收到一個系統(tǒng)通知。jdk提供了PhantomReference來實(shí)現(xiàn)虛引用。
回收方法區(qū)
在hotspot中,大家更原因?qū)⑵浞Q為永久代(jdk1.8廢除永久代,metaspace元空間出現(xiàn),咱不討論)。永久代主要回收兩部分內(nèi)容:廢棄常量和無用的類。以一個字符串“abc”已經(jīng)進(jìn)入了常量池中,但是系統(tǒng)中沒有一個String對象指向abc的,也沒有其他地方引用了這個字面量,當(dāng)發(fā)生垃圾回收時,并且必要的話,這個常量就會被系統(tǒng)清理出常量池。常量池中的其他類(接口)、方法、字段的符號引用也與此類似。判定一個常量是否存活比較簡單,而要判定一個類是否是無用的類的條件就要苛刻很多。必須得滿足以下三個條件:1.該類的所有實(shí)例已經(jīng)被回收2.加載該類的ClassLoader已經(jīng)被回收3.該類對應(yīng)的Class對象沒有在其他任何地方被引用,無法再任何地方通過反射訪問該類的方法。只有滿足以上三個條件,這個類才有可能被回收。是否對類進(jìn)行回收,hotspot虛擬機(jī)提供了-Xnoclassgc參數(shù)進(jìn)行控制。
標(biāo)記-清除算法
標(biāo)記清除算法(Mark-sweep)是最基礎(chǔ)的算法,其過程如同名字一樣分為標(biāo)記和清除兩個階段:首先標(biāo)記出所有需要回收的對象,標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對象。之所以說它是最基礎(chǔ)的算法是因?yàn)楹罄m(xù)的收集算法都是在它的基礎(chǔ)上對其不足進(jìn)行改進(jìn)而得到的(但同時也會暴露其他問題,沒有最合適,只有更合適)。它的不足主要有兩個,一個是效率問題,標(biāo)記和清除的效率都不高。另一個是空間問題,會產(chǎn)生大量的內(nèi)存碎片,碎片太多可能會導(dǎo)致以后分配大對象無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動作。執(zhí)行過程如下圖所示:
復(fù)制算法
為了解決標(biāo)記清除算法的效率問題,復(fù)制算法出現(xiàn)了。他將可用內(nèi)存按大小分為大小相等的兩份,每次只使用其中的一份,當(dāng)其中一份內(nèi)存使用完了,就將還存活著的對象復(fù)制到另外一塊上面,然后再將已使用的那份內(nèi)存空間一次清理掉。這種算法只對整個半?yún)^(qū)進(jìn)行內(nèi)存回收,內(nèi)存分配時也不再需要考慮內(nèi)存碎片的問題,只要每次移動堆頂指針,按順序分配內(nèi)存即可,實(shí)現(xiàn)簡單,運(yùn)行高效。缺點(diǎn)是將可用內(nèi)存縮小了一半,代價太高。執(zhí)行過程如下所示:
目前的商業(yè)虛擬機(jī)都采用復(fù)制算法來回收新生代。新生代中的對象98%都是朝生夕死的,所以我們不需要嚴(yán)格的按照1:1的比例來劃分內(nèi)存空間。目前的商用虛擬機(jī)都將新生代內(nèi)存劃分為Eden和兩塊Survivor空間,其比例默認(rèn)為8:1:1,每次只使用Eden和其中一塊Survivor空間。當(dāng)回收內(nèi)存時,會將Eden和已經(jīng)使用的Survivor空間中存活的對象一次性的復(fù)制到另外一塊Survivor空間,復(fù)制完成然后清理Eden區(qū)域和剛才用過的Survivor空間。每次只有10%的內(nèi)存不可用算是對復(fù)制算法的一個優(yōu)化,是可以被接受的。另外前面有提到,一般場景98%的對象都是朝生夕死,但是我們沒有辦法保證Eden和其中一塊使用的Survivor空間存活的對象一定比另外一塊未使用的Survivor空間小,如果未使用的Survivor空間不夠用時,需要依賴?yán)夏甏M(jìn)行分配擔(dān)保(Handle Promotion)。
標(biāo)記-整理算法
復(fù)制收集算法尤其適合新生代,因?yàn)樾律鷮ο笠话闱榫诚露际浅λ赖?。但是如果在對象存活率較高甚至極端情況下達(dá)到100%的存活率,就要進(jìn)行較多的復(fù)制操作,效率將會變得極其低下,又因?yàn)樾枰~外的空間進(jìn)行擔(dān)保,所以老年代不會選用復(fù)制算法(老年代的對象一般都活的比較久)。
根據(jù)老年代對象的特點(diǎn),就提出了標(biāo)記整理算法。第一步仍是標(biāo)記,但后續(xù)步驟不再是清除而是整理,讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存。執(zhí)行過程如下圖所示:
分代收集算法
前面有提到新生代和老年代,其實(shí)是根據(jù)對象存活周期的不同來劃分的。新生代中,每次垃圾收集的對象都會有大批死去,只有少量存活,那就用復(fù)制算法,只需要少量存活對象的復(fù)制成本就可以完成收集。而老年代對象存活率高,又沒有額外空間對它進(jìn)行分配擔(dān)保,就必須使用標(biāo)記整理和表表情清除算法來進(jìn)行回收,這就是所謂的分代收集算法。
從可達(dá)性分析中從GC Roots節(jié)點(diǎn)找引用鏈這個操作為例,可以作為GC Roots的節(jié)點(diǎn)主要為常量、靜態(tài)、和棧幀中的本地變量表,現(xiàn)在很多程序僅僅方法區(qū)就有數(shù)百兆,如果逐個檢查里面的引用,那么必然會消耗很多的時間。另外,可達(dá)性分析對執(zhí)行時間的敏感還體現(xiàn)在GC停頓上,因?yàn)檫@項(xiàng)分析工作必須在一個確保一致性的快照中進(jìn)行。這個一致性的意思是整個分析期間這個程序看起來就像被凍結(jié)在某個時間點(diǎn)一樣,不可以出現(xiàn)分析過程中對象引用關(guān)系還在不斷變化的情況,這點(diǎn)如果不滿足的話結(jié)果準(zhǔn)確性就無從談起了。這點(diǎn)是導(dǎo)致gc進(jìn)行時程序必須停頓所有java工作線程的一個重要原因,Sun公司將其稱為STW(Stop the World)。聽起來很酷,但這很有可能是造成接口超時等其他問題的罪魁禍?zhǔn)住?/p> 垃圾收集器
如果說收集算法是垃圾回收的方法論,那么垃圾收集器就是垃圾回收的具體實(shí)現(xiàn)。虛擬機(jī)規(guī)范并沒有規(guī)定虛擬機(jī)如何去實(shí)現(xiàn),因此不同廠商不同版本的虛擬機(jī)提供的垃圾收集器都可能會有很大差別。本文只討論JDK1.7Update14之后的hotspot虛擬機(jī),如下圖所示:
Young generation代表年輕代,Tenured generation代表老年代??偣灿?款收集器,他們都負(fù)責(zé)回收自己所在的區(qū)域,收集器之間的實(shí)線代表著這兩款收集器可以合作,一起收集整個堆內(nèi)存。其中G1是JDK1.7之后才正式被oracle定義為商用虛擬機(jī),G1回收整個新生代和部分老年代比較特殊,本文暫不討論。
在講解收集器之前,先給大家介紹下32位和64位的jdk。32位系統(tǒng)只可以裝32位jdk,64位系統(tǒng)兩者都可以裝,但推薦安裝64位jdk。在32位的jdk下,虛擬機(jī)的模式是可選的,默認(rèn)為client模式,可以通過修改配置文件為server模式,但在64位的jdk下,虛擬機(jī)只能為server模式。目前大部分服務(wù)器甚至很多個人電腦都是64bit,也就是默認(rèn)server模式。
在接下來介紹的六款收集器中,只有serial和serial old是單線程回收內(nèi)存的收集器。其他都是多條線程回收內(nèi)存的,有的是并行,有的是并發(fā),在介紹這幾款收集器之前,我們先講解下并行和并發(fā)在垃圾回收這個上衣文語境中所代表的含義:
1.并行:多條垃圾收集線程并行工作,但用戶線程仍在等待狀態(tài) 2.并發(fā):用戶線程與垃圾收集線程同時執(zhí)行(可能會交替執(zhí)行),用戶程序在運(yùn)行在一部分cpu上,而垃圾回收運(yùn)行在另一部分cpu上。
serial收集器
serial收集器是一款歷史很悠久的收集器,在jdk1.3之前是新生代的唯一選擇。這是一個單線程的收集器,它只會占用一個cpu啟動一個線程去回收內(nèi)存,但它也是會導(dǎo)致stw的。下圖為serila和serial old收集器工作的示意圖:
直到今天,它依然是虛擬機(jī)運(yùn)行在client模式下默認(rèn)的新生代收集器,在用戶的桌面場景應(yīng)用中,分配給虛擬機(jī)管理的內(nèi)存一般不會太大,停頓時間完全可以控制在一百毫秒以內(nèi),只要不是頻繁發(fā)生,這點(diǎn)停頓是可以接受的。
parnew收集器
parnew收集器就是serial收集器的多線程版本,除了使用多個線程進(jìn)行垃圾回收之外,其余行為包括serial收集器所有可用的控制參數(shù)、收集算法、stw、對象分配規(guī)則、回收策略斗魚serial一模一樣。在實(shí)現(xiàn)上,兩種收集器也共用了很多代碼。parnew和serial Old收集器工作的示意圖:
parnew收集器是虛擬機(jī)server模式下默認(rèn)的新生代虛擬機(jī),但是它和serial相比除了是多線程收集外并沒有其他的特色,其中一個與性能無關(guān)但很重要的原因,目前除了serial收集器,它是唯一一個可以cms共同工作的一個收集器。在jdk1.5,hotspot推出了一款劃時代意義的垃圾收集器-----cms收集器,這款收集器是真正意義上的第一款并發(fā)收集器,他第一次實(shí)現(xiàn)了讓用戶線程和垃圾收集線程基本上同時工作(但stw還是存在的,稍后會講解cms收集器的內(nèi)容)。不幸的是,cms作為老年代的收集器,卻只能和serial和parnew收集器共同工作,parnew收集器也是使用-XX:+UseConcMarkSweepGC參數(shù)后的默認(rèn)新生代收集器。
parallel scavenge收集器
parallel scavenge收集器也是一個新生代的收集器和parnew大致一樣,但它的關(guān)注點(diǎn)與其他收集器不同。其他收集器都是盡可能地縮短垃圾收集時用戶線程的停頓時間,而parallel scavenge收集器的目的是為了達(dá)到一個可控制的吞吐量(Throughput)。吞吐量就是cpu用于運(yùn)行用戶代碼的時間與cpu工作時間的比值,即吞吐量的計(jì)算應(yīng)該為:吞吐量=用戶線程的工作時間/(用戶線程的工作時間和垃圾收集的時間),虛擬機(jī)總共運(yùn)行100分鐘,垃圾收集器運(yùn)行2分鐘,那吞吐量就是98%。停頓時間越短,就越適合需要與用戶交互的系統(tǒng)程序,可以良好的提升用戶體驗(yàn),而吞吐量越高說明cpu的利用效率越高,可以盡快完成的程序的運(yùn)算任務(wù),主要適合后臺運(yùn)算而不需要太多的交互任務(wù)。
parallel scavenge收集器提供了兩個參數(shù)用于控制吞吐量,分別是控制最大垃圾收集停頓時間的-XX:MaxGCPauseMills和直接設(shè)置吞吐量大小的-XX:GCTimeRatio參數(shù)。XX:MaxGCPauseMills允許的值是一個大于0的毫秒數(shù),收集器盡可能保證內(nèi)存回收花費(fèi)的時間不超過此值,但并不是將此參數(shù)設(shè)的越小系統(tǒng)的垃圾收集速度就越快,它是犧牲了新生代空間和吞吐量來換取的:收集500MB的新生代肯定比收集1GB的新生代快,但換來的代價是 ygc會更頻繁一些。原來10秒一次ygc,一次停頓100ms,現(xiàn)在5秒一次ygc,一次停頓60ms。停頓時間在下降,但吞吐量也降下來了。GCTimeRatio參數(shù)的值應(yīng)當(dāng)是一個大于0小于100的整數(shù),也就是垃圾收集時間占總時間的比率,如果把參數(shù)設(shè)置為9,那允許的最大gc時間就占總時間的10%,計(jì)算方法是這樣子的(1/(1+9))。
parallel scavenge收集器還提供了一個參數(shù)-XX:+UserAdaptiveSizePolicy,這是一個開關(guān)參數(shù),當(dāng)這個參數(shù)打開后,就不需要手工指定新生代的大小、Eden與Survivor的比例、晉升老年代對象的大小等參數(shù)了,虛擬機(jī)會根據(jù)運(yùn)行情況尋找最合適的配比。如果對于收集器運(yùn)作不太了解,可以只配置-Xmx設(shè)置最大堆,配置MaxGCPauseMills設(shè)置最大停頓時間或GCTimeRatios來設(shè)置吞吐量給虛擬機(jī)來建立一個優(yōu)化目標(biāo),具體的細(xì)節(jié)參數(shù)交給parallel scavenge收集器來自動調(diào)配。
serial old 收集器
serial Old是serial收集起的老年代版本,同樣的他也是一個單線程收集器,使用標(biāo)記整理算法(注意新生代使用的都是復(fù)制算法,前面有提到),這個收集器的主要意義也是給client模式下虛擬機(jī)來使用。如果在server模式下,它還有兩大用途:一種用途是JDK1.5以及之前的版本中配合parallel scavenge使用的,另一種用途就是作為cms收集器的備用方案,在這款收集器發(fā)生Concurrent Mode Failure時切換為Serial old收集器
parallel old收集器
parallel old收集器是parallel scavenge收集器的老年代版本,使用多線程和標(biāo)記整理算法,在jdk1.6才開始提供的。所以在此之前新生代的parallel scavenge一直處于十分尷尬的狀態(tài),如果新生代使用了parallel scavenge收集器,老年代只能與serial old配合(parallel scavenge收集器無法與cms工作)。由于serila old收集器在服務(wù)器應(yīng)用性能上的拖累,使用parallel scavenge收集器也未必能夠獲得吞吐量最大化的效果,其原因是因?yàn)閟erial old是單線程的無法充分利用服務(wù)器多cpu的處理能力,在老年代很大且硬件比較高級的環(huán)境中,這種組合的吞吐量還不一定有parnew+cms的組合給力。直到parallel old收集器出現(xiàn)后,吞吐量優(yōu)先收集器才有了比較名副其實(shí)的組合,在注重吞吐量和cpu資源非常敏感的情況下,都可以優(yōu)先考慮parallel old+parallel scavenge收集器,其工作過程如下所示:
cms收集器
cms(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標(biāo)的收集器。目前很大一部分的java應(yīng)用集中在互聯(lián)網(wǎng)站或者B/S的系統(tǒng)服務(wù)端上,這類應(yīng)用非常重視響應(yīng)速度,希望停頓時間最短,cms收集器就非常符合這類需求。
cms是一款基于標(biāo)記清除算法的收集器,在jdk1.5中提出,也是hotspot第一款并發(fā)收集器。它的運(yùn)作過程相對于前面幾款收集器復(fù)雜一些,正題分為4個步驟:
7. 初始標(biāo)記(CMS initial mark):初始標(biāo)記需要stw,它僅僅只是標(biāo)記下GC Roots能直接關(guān)聯(lián)到的對象,速度很快。 7. 并發(fā)標(biāo)記(CMS concurrent mark):不需要stw,這個階段是根據(jù)初始標(biāo)記得到的存活對象進(jìn)行遞歸標(biāo)記可達(dá)的對象。 8. 重新標(biāo)記(CMS remark):并發(fā)標(biāo)記這個階段是并發(fā)執(zhí)行的(用戶線程也在工作),可能發(fā)生對象晉升到老年代或者大對象直接分配老年代或者新老年代對象的引用關(guān)系被更新等等,對于這些變動的引用關(guān)系需要重新標(biāo)記更正,并且發(fā)生stw,會比初始標(biāo)記時間要長,但遠(yuǎn)比并發(fā)標(biāo)記時間短。 9. 并發(fā)清除(CMS concurrent sweep):并發(fā)清除就很簡單了,之前已經(jīng)整理出存活對象,直接清除就是了。
由于最耗時間的并發(fā)標(biāo)記和并發(fā)清除都可以和用戶線程一起工作,所以總體上來說cms收集器的內(nèi)存回收過程是與用戶線程一起兵法執(zhí)行的。cms的工作流程如下圖所示:
cms是一款優(yōu)秀的收集器,并發(fā)收集和低停頓都是他的特點(diǎn),但它有3個明顯的缺點(diǎn):
cms收集器對cpu資源非常敏感,因?yàn)槭遣l(fā)的會去搶奪cpu資源,造成應(yīng)用程序突然變慢,總吞吐量降低的情況。
cms基于標(biāo)記清除算法實(shí)現(xiàn)的,無法清除浮動垃圾,可能出現(xiàn)Concurrent Mode Failure失敗導(dǎo)致另一次Full GC的產(chǎn)生。由于cms的并發(fā)清理階段用戶線程還在運(yùn)行著,必然會有新的垃圾不斷產(chǎn)生,這部分垃圾出現(xiàn)在標(biāo)記之后,cms無法在當(dāng)次收集過程回收它,只能留到下一次回收,因此cms不能像其他年老代收集器一樣等到年老代幾乎被填滿了再進(jìn)行收集,需要預(yù)留一部分空間提供并發(fā)清理時的程序運(yùn)作使用。在jdk1.5中cms收集器當(dāng)老年代使用了68%的空間后就會被激活,在jdk1.5之后,已經(jīng)將閾值提升至92%。如果Cms預(yù)留內(nèi)存不夠用,將會發(fā)生Concurrent Mode Failure,這時將會啟動備用方案,臨時啟動serial old來進(jìn)行老年代的手機(jī),這樣停頓時間就很長了。這個閾值可以通過參數(shù)-XX:CMSInitiatingOccupancyFraction來設(shè)置,設(shè)置的太高,老年代增長的又比較快,就會導(dǎo)致大量Concurrent Mode failure出現(xiàn),性能反而降低。
cms是基于一款標(biāo)記清楚算法實(shí)現(xiàn)的收集器,這種算法會導(dǎo)致大量空間碎片產(chǎn)生。這將會給分配大對象帶來麻煩,往往老年代空間還很多,但是無法找到足夠大的連續(xù)空間來分配當(dāng)前對象,不得不觸發(fā)一次Full GC。為了解決這個問題,CMS收集器提供了一個開關(guān)參數(shù):-XX:+UseCMSCompactAtFullCollection(默認(rèn)開啟),用于cms收集器頂不住要進(jìn)行fullGC 時開啟內(nèi)存碎片的合并整理過程,這個過程是無法并發(fā)的,空間碎片問題沒有了,但停頓時間不得不變長。cms還提供了一個參數(shù)-XX:CMSFullGCsBeforeCompaction來設(shè)置執(zhí)行多少次不壓縮的Full GC后跟著來一次壓縮整理。
總結(jié)本篇博客主要講解了垃圾回收算法,內(nèi)存區(qū)域的一些細(xì)節(jié)和收集器大致的工作流程。通過分析比較各個收集器,我們發(fā)現(xiàn)沒有最好的收集器組合,更沒有萬能的收集器組合。我們只能通過場景分析來定最合適的收集器。
下節(jié)預(yù)告1.gc日志的閱讀
2.內(nèi)存分配和回收策略
3.虛擬機(jī)提供的性能監(jiān)控工具
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/68989.html
摘要:騰訊特約作者姚潮生首先以一個內(nèi)存泄露實(shí)例來開始本節(jié)基礎(chǔ)概念的內(nèi)容。堆內(nèi)存用于存放所有由創(chuàng)建的對象內(nèi)容包括該對象其中的所有成員變量和數(shù)組?;氐轿覀兊膯栴},為什么內(nèi)存會泄露堆內(nèi)存中的長生命周期的對象持有短生命周期對象的強(qiáng)軟引用,盡管 騰訊Bugly特約作者: 姚潮生 首先以一個內(nèi)存泄露實(shí)例來開始本節(jié)基礎(chǔ)概念的內(nèi)容。 實(shí)例1:單例導(dǎo)致內(nèi)存對象無法釋放而泄露 showImg(http://i....
摘要:對比操作前后的來定位內(nèi)存泄露的根因所在。手機(jī)管家內(nèi)存泄露每日監(jiān)控方案目前手機(jī)管家的內(nèi)存泄露每日監(jiān)控會自動運(yùn)行并輸出是否存在疑似泄露的報(bào)告郵件,不論泄露對象的大小。 騰訊Bugly特約作者: 姚潮生 最原始的內(nèi)存泄露測試 重復(fù)多次操作關(guān)鍵的可疑的路徑,從內(nèi)存監(jiān)控工具中觀察內(nèi)存曲線,是否存在不斷上升的趨勢且不會在程序返回時明顯回落。這種方式可以發(fā)現(xiàn)最基本,也是最明顯的內(nèi)存泄露問題,對用戶價...
摘要:對比操作前后的來定位內(nèi)存泄露的根因所在。手機(jī)管家內(nèi)存泄露每日監(jiān)控方案目前手機(jī)管家的內(nèi)存泄露每日監(jiān)控會自動運(yùn)行并輸出是否存在疑似泄露的報(bào)告郵件,不論泄露對象的大小。 騰訊Bugly特約作者: 姚潮生 最原始的內(nèi)存泄露測試 重復(fù)多次操作關(guān)鍵的可疑的路徑,從內(nèi)存監(jiān)控工具中觀察內(nèi)存曲線,是否存在不斷上升的趨勢且不會在程序返回時明顯回落。這種方式可以發(fā)現(xiàn)最基本,也是最明顯的內(nèi)存泄露問題,對用戶價...
摘要:當(dāng)一個實(shí)例被創(chuàng)建的時候,它最初被存放在堆內(nèi)存空間的年輕代的區(qū)中。老年代或者永久代是堆內(nèi)存的第二個邏輯部分。在垃圾回收過程中掃描屬于部分的堆內(nèi)存。一旦實(shí)例從堆內(nèi)存中刪除了,它們原來的位置將空出來給以后分配實(shí)例使用。 本文非原創(chuàng),翻譯自How Java Garbage Collection Works?在Java中為對象分配和釋放內(nèi)存空間都是由垃圾回收線程自動執(zhí)行完成的。和C語言不一樣的是...
摘要:在架構(gòu)中,堆內(nèi)存和垃圾回收器這兩個部分和垃圾回收相關(guān)。堆內(nèi)存在的內(nèi)存模型中,最重要的是要了解堆內(nèi)存的概念。在垃圾回收的過程中,這些對象將被從堆內(nèi)存中清除,同時它們的空間也就被回收了。 本文非原創(chuàng),翻譯自Java Garbage Collection introduction在Java中為對象分配和釋放內(nèi)存空間都是由垃圾回收線程自動執(zhí)行完成的。和C語言不一樣的是Java程序員不需要手動寫...
閱讀 1917·2021-11-25 09:43
閱讀 1423·2021-11-22 14:56
閱讀 3288·2021-11-22 09:34
閱讀 2027·2021-11-15 11:37
閱讀 2282·2021-09-01 10:46
閱讀 1408·2019-08-30 15:44
閱讀 2304·2019-08-30 13:15
閱讀 2403·2019-08-29 13:07