摘要:復(fù)制這一工作所花費(fèi)的時(shí)間,在對(duì)象存活率達(dá)到一定程度時(shí),將會(huì)變的不可忽視。針對(duì)老年代老年代的特點(diǎn)是區(qū)域較大,對(duì)像存活率高。這種情況,存在大量存活率高的對(duì)像,復(fù)制算法明顯變得不合適。
GC(Garbage Collection)即Java垃圾回收機(jī)制,是Java與C++的主要區(qū)別之一,作為Java開發(fā)者,一般不需要專門編寫內(nèi)存回收和垃圾清理代碼,對(duì)內(nèi)存泄露和溢出的問題,也不需要像C++程序員那樣戰(zhàn)戰(zhàn)兢兢,就是因?yàn)镴ava有這個(gè)方便的機(jī)制。
為了對(duì)GC有一個(gè)直觀的認(rèn)識(shí),先來一張圖:
對(duì)圖中各種名詞不熟悉的話,請(qǐng)參照我的上一篇文章:JVM小結(jié)
JVM在進(jìn)行GC時(shí),并非每次都對(duì)上面三個(gè)內(nèi)存區(qū)域一起回收的,大部分時(shí)候回收的都是指新生代。因此GC按照回收的區(qū)域又分了兩種類型,一種是普通GC(minor GC),一種是全局GC(major GC or Full GC)
普通GC(minor GC):只針對(duì)新生代區(qū)域的GC。
全局GC(major GC or Full GC):針對(duì)年老代的GC,偶爾伴隨對(duì)新生代的GC以及對(duì)永久代的GC。
四大算法 1. 復(fù)制算法(Copying)年輕代中使用的是Minor GC,這種GC算法采用的是復(fù)制算法(Copying)。
此圖代表了堆的內(nèi)存結(jié)構(gòu)
HotSpot JVM把年輕代分為了三部分:1個(gè)Eden區(qū)和2個(gè)Survivor區(qū)(分別叫from和to)。默認(rèn)比例為8:1:1,一般情況下,新創(chuàng)建的對(duì)象都會(huì)被分配到Eden區(qū)(一些大對(duì)象特殊處理),這些對(duì)象經(jīng)過第一次Minor GC后,如果仍然存活,將會(huì)被移到Survivor區(qū)。對(duì)象在Survivor區(qū)中每熬過一次Minor GC,年齡就會(huì)增加1歲,當(dāng)它的年齡增加到一定程度時(shí),就會(huì)被移動(dòng)到年老代中。因?yàn)槟贻p代中的對(duì)象基本都是朝生夕死的(80%以上),所以在年輕代的垃圾回收算法使用的是復(fù)制算法,復(fù)制算法的基本思想就是將內(nèi)存分為兩塊,每次只用其中一塊,當(dāng)這一塊內(nèi)存用完,就將還活著的對(duì)象復(fù)制到另外一塊上面。復(fù)制算法不會(huì)產(chǎn)生內(nèi)存碎片。
在GC開始的時(shí)候,對(duì)象只會(huì)存在于Eden區(qū)和名為From的Survivor區(qū),Survivor區(qū)To是空的。緊接著進(jìn)行GC,Eden區(qū)中所有存活的對(duì)象都會(huì)被復(fù)制到To,而在From區(qū)中,仍存活的對(duì)象會(huì)根據(jù)他們的年齡值來決定去向。年齡達(dá)到一定值(年齡閾值,可以通過-XX:MaxTenuringThreshold來設(shè)置)的對(duì)象會(huì)被移動(dòng)到年老代中,沒有達(dá)到閾值的對(duì)象會(huì)被復(fù)制到To區(qū)域。經(jīng)過這次GC后,Eden區(qū)和From區(qū)已經(jīng)被清空。這個(gè)時(shí)候,From和To會(huì)交換他們的角色,也就是新的To就是上次GC前的From,新的From就是上次GC前的To。不管怎樣,都會(huì)保證名為To的Survivor區(qū)域是空的。Minor GC會(huì)一直重復(fù)這樣的過程,直到To區(qū)被填滿,To區(qū)被填滿之后,會(huì)將所有對(duì)象移動(dòng)到年老代中。
-XX:MaxTenuringThreshold 設(shè)置對(duì)象在新生代中存活的次數(shù)
因?yàn)镋den區(qū)對(duì)象一般存活率較低,一般的,使用兩塊10%的內(nèi)存作為空閑和活動(dòng)區(qū)間,而另外80%的內(nèi)存,則是用來給新建對(duì)象分配內(nèi)存的。一旦發(fā)生GC,將10%的from活動(dòng)區(qū)間與另外80%中存活的eden對(duì)象轉(zhuǎn)移到10%的to空閑區(qū)間,接下來,將之前90%的內(nèi)存全部釋放,以此類推。
劣勢(shì):
復(fù)制算法彌補(bǔ)了標(biāo)記/清除算法中,內(nèi)存布局混亂的缺點(diǎn)。不過與此同時(shí),它的缺點(diǎn)也是相當(dāng)明顯的:
它浪費(fèi)了一半的內(nèi)存。
如果對(duì)象的存活率很高,我們可以極端一點(diǎn),假設(shè)是100%存活,那么我們需要將所有對(duì)象都復(fù)制一遍,并將所有引用地址重置一遍。復(fù)制這一工作所花費(fèi)的時(shí)間,在對(duì)象存活率達(dá)到一定程度時(shí),將會(huì)變的不可忽視。 所以從以上描述不難看出,復(fù)制算法要想使用,最起碼對(duì)象的存活率要非常低才行,而且最重要的是,我們必須要克服50%內(nèi)存的浪費(fèi)。
2. 標(biāo)記清除(Mark-Sweep)老年代一般是由標(biāo)記清除或者是標(biāo)記清除與標(biāo)記整理的混合實(shí)現(xiàn)。
當(dāng)堆中的有效內(nèi)存空間(available memory)被耗盡的時(shí)候,就會(huì)停止整個(gè)程序(也被稱為stop the world),然后進(jìn)行兩項(xiàng)工作,第一項(xiàng)則是標(biāo)記,第二項(xiàng)則是清除。
標(biāo)記:從引用根節(jié)點(diǎn)開始標(biāo)記所有被引用的對(duì)象。標(biāo)記的過程其實(shí)就是遍歷所有的GC Roots,然后將所有GC Roots可達(dá)的對(duì)象 標(biāo)記為存活的對(duì)象。
清除:遍歷整個(gè)堆,把未標(biāo)記的對(duì)象清除。
通俗來講,就是當(dāng)程序運(yùn)行期間,若可以使用的內(nèi)存被耗盡的時(shí)候,GC線程就會(huì)被觸發(fā)并將程序暫停,隨后將依舊存活的對(duì)象標(biāo)記一遍,最終再將堆中所有沒被標(biāo)記的對(duì)象全部清除掉,接下來便讓程序恢復(fù)運(yùn)行。
缺點(diǎn):
效率比較低(遞歸與全堆對(duì)象遍歷),而且在進(jìn)行GC的時(shí)候,需要停止應(yīng)用程序,這會(huì)導(dǎo)致用戶體驗(yàn)非常差。
這種方式清理出來的空閑內(nèi)存是不連續(xù)的,我們的死亡對(duì)象都是隨即的出現(xiàn)在內(nèi)存的各個(gè)角落的,把它們清除之后,內(nèi)存的布局自然會(huì)散亂。而為了應(yīng)付這一點(diǎn),JVM就不得不維持一個(gè)內(nèi)存的空閑列表,這又是一種開銷。而且在分配數(shù)組對(duì)象的時(shí)候,尋找連續(xù)的內(nèi)存空間會(huì)有難度。
3. 標(biāo)記壓縮(Mark-Compact)老年代一般是由標(biāo)記清除或者是標(biāo)記清除與標(biāo)記壓縮的混合實(shí)現(xiàn)。
在整理壓縮階段,不再對(duì)標(biāo)記的對(duì)像做回收,而是通過所有存活對(duì)像都向一端移動(dòng),然后直接清除邊界以外的內(nèi)存。
可以看到,標(biāo)記的存活對(duì)象將會(huì)被整理,按照內(nèi)存地址依次排列,而未被標(biāo)記的內(nèi)存會(huì)被清理掉。如此一來,當(dāng)我們需要給新對(duì)象分配內(nèi)存時(shí),JVM只需要持有一個(gè)內(nèi)存的起始地址即可,這比維護(hù)一個(gè)空閑列表顯然少了許多開銷。
標(biāo)記/整理算法不僅可以彌補(bǔ)標(biāo)記/清除算法當(dāng)中,內(nèi)存區(qū)域分散的缺點(diǎn),也消除了復(fù)制算法當(dāng)中,內(nèi)存減半的高額代價(jià)。
劣勢(shì):標(biāo)記/整理算法唯一的缺點(diǎn)就是效率也不高,不僅要標(biāo)記所有存活對(duì)象,還要整理所有存活對(duì)象的引用地址。從效率上來說,標(biāo)記/整理算法要低于復(fù)制算法。
4. 標(biāo)記清除壓縮(Mark-Sweep-Compact) 多提一嘴:引用計(jì)數(shù)法這種算法最直接簡(jiǎn)單,它維護(hù)了一個(gè)引用計(jì)數(shù)器,對(duì)象被引用一次計(jì)數(shù)器+1,少一次-1,如果計(jì)數(shù)器為0則對(duì)象就被視為垃圾進(jìn)行回收。
注:JVM的實(shí)現(xiàn)一般不采用這種方式。
劣勢(shì):
每次對(duì)對(duì)象賦值時(shí)均需維護(hù)計(jì)數(shù)器,且計(jì)數(shù)器本身有一定消耗。
較難處理循環(huán)引用。
小結(jié)內(nèi)存效率:復(fù)制算法>標(biāo)記清除算法>標(biāo)記整理算法(此處的效率只是簡(jiǎn)單的對(duì)比時(shí)間復(fù)雜度,實(shí)際情況不一定如此)。
內(nèi)存整齊度:復(fù)制算法==標(biāo)記整理算法>標(biāo)記清除算法。
內(nèi)存利用率:標(biāo)記整理算法==標(biāo)記清除算法>復(fù)制算法。
可以看出,效率上來說,復(fù)制算法最快,但是卻浪費(fèi)了太多內(nèi)存,而為了盡量兼顧上面所提到的三個(gè)指標(biāo),標(biāo)記/整理算法相對(duì)來說更平滑一些,但效率上依然不盡如人意,它比復(fù)制算法多了一個(gè)標(biāo)記的階段,又比標(biāo)記/清除多了一個(gè)整理內(nèi)存的過程。
有沒有最好的算法呢?只能說:沒有最好的,只有最適合的——分代收集:
針對(duì)年輕代年輕代特點(diǎn)是區(qū)域相對(duì)老年代較小,對(duì)像存活率低。這種情況復(fù)制算法的回收整理,速度是最快的。復(fù)制算法的效率只和當(dāng)前存活對(duì)像大小有關(guān),因而很適用于年輕代的回收。而復(fù)制算法內(nèi)存利用率不高的問題,通過hotspot中的兩個(gè)survivor的設(shè)計(jì)得到緩解。
針對(duì)老年代老年代的特點(diǎn)是區(qū)域較大,對(duì)像存活率高。
這種情況,存在大量存活率高的對(duì)像,復(fù)制算法明顯變得不合適。一般是由標(biāo)記清除或者是標(biāo)記清除與標(biāo)記整理的混合實(shí)現(xiàn)。
Mark階段的開銷與存活對(duì)像的數(shù)量成正比,這點(diǎn)上說來,對(duì)于老年代,標(biāo)記清除或者標(biāo)記整理有一些不符,但可以通過多核/線程利用,對(duì)并發(fā)、并行的形式提標(biāo)記效率。
Sweep階段的開銷與所管理區(qū)域的大小形正相關(guān),但Sweep“就地處決”的特點(diǎn),回收的過程沒有對(duì)像的移動(dòng)。使其相對(duì)其它有對(duì)像移動(dòng)步驟的回收算法,仍然是效率最好的。但是需要解決內(nèi)存碎片問題。
Compact階段的開銷與存活對(duì)像的數(shù)據(jù)成開比,如上一條所描述,對(duì)于大量對(duì)像的移動(dòng)是很大開銷的,做為老年代的第一選擇并不合適。
基于上面的考慮,老年代一般是由標(biāo)記清除或者是標(biāo)記清除與標(biāo)記整理的混合實(shí)現(xiàn)。以hotspot中的CMS回收器為例,CMS是基于Mark-Sweep實(shí)現(xiàn)的,對(duì)于對(duì)像的回收效率很高,而對(duì)于碎片問題,CMS采用基于Mark-Compact算法的Serial Old回收器做為補(bǔ)償措施:當(dāng)內(nèi)存回收不佳(碎片導(dǎo)致的Concurrent Mode Failure時(shí)),將采用Serial Old執(zhí)行Full GC以達(dá)到對(duì)老年代內(nèi)存的整理。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/68363.html
摘要:序本主要研究一下的在進(jìn)行的都采用了的技術(shù)面向的垃圾收集器,它的主要有包括這個(gè)階段用到了進(jìn)行包括在進(jìn)行的時(shí)候采用了的技術(shù)全稱為,其要點(diǎn)如下的過程就是遍歷標(biāo)記,采用的是三色標(biāo)記算法,這三種顏色為表示還未訪問到訪問到但是它用到的引用還沒 序 本主要研究一下Garbage Collector的SATB CMS、G1、Shenandoah在進(jìn)行concurrent marking的都采用了SAT...
摘要:當(dāng)時(shí),如果老生區(qū)大小超過設(shè)定的值時(shí),就會(huì)報(bào)錯(cuò)。一般是無限制增長(zhǎng)的數(shù)組無限制設(shè)置屬性和值大循環(huán)等出處林小新。這部分由于攻城獅并為深入,可以參考如何定位的內(nèi)存泄漏內(nèi)存泄漏以及定位 showImg(https://segmentfault.com/img/bVbnysD?w=649&h=658);↑開局一張圖,故事全靠編↑ 從一次宕機(jī)說起 這是一個(gè)很狗血的故事,故事的開頭是一個(gè)項(xiàng)目,這個(gè)項(xiàng)...
摘要:一個(gè)對(duì)象若只被弱引用所引用,則被認(rèn)為是不可訪問或弱可訪問的,并因此可能在任何時(shí)刻被回收。也就是說,一旦不再需要,里面的鍵名對(duì)象和所對(duì)應(yīng)的鍵值對(duì)會(huì)自動(dòng)消失,不用手動(dòng)刪除引用。如果有錯(cuò)誤或者不嚴(yán)謹(jǐn)?shù)牡胤剑?qǐng)務(wù)必給予指正,十分感謝。 前言 我們先從 WeakMap 的特性說起,然后聊聊 WeakMap 的一些應(yīng)用場(chǎng)景。 特性 1. WeakMap 只接受對(duì)象作為鍵名 const map = ...
閱讀 3309·2023-04-26 02:40
閱讀 4661·2021-09-22 15:22
閱讀 1604·2021-09-22 10:02
閱讀 3492·2021-08-11 10:23
閱讀 1401·2019-08-30 15:55
閱讀 2504·2019-08-30 12:48
閱讀 595·2019-08-30 11:04
閱讀 715·2019-08-29 16:29