溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

怎么理解Java常見(jiàn)知識(shí)點(diǎn)中的垃圾回收機(jī)制

發(fā)布時(shí)間:2021-11-20 15:48:55 來(lái)源:億速云 閱讀:136 作者:柒染 欄目:軟件技術(shù)

這篇文章將為大家詳細(xì)講解有關(guān)怎么理解Java常見(jiàn)知識(shí)點(diǎn)中的垃圾回收機(jī)制,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。

Java堆中存放著大量的Java對(duì)象實(shí)例,在垃圾收集器回收內(nèi)存前,第一件事情就是確定哪些對(duì)象是“活著的”,哪些是可以回收的。

Java中Stop-The-World機(jī)制簡(jiǎn)稱(chēng)STW,是在執(zhí)行垃圾收集算法時(shí),Java應(yīng)用程序的其他所有線程都被掛起(除了垃圾收集幫助器之外)。Java中一種全局暫?,F(xiàn)象,全局停頓,所有Java代碼停止,native代碼可以執(zhí)行,但不能與JVM交互;這些現(xiàn)象多半是由于gc引起。

一. 判斷對(duì)象是否存活的算法

1. 引用計(jì)數(shù)算法(基本棄用)

引用計(jì)數(shù)算法是判斷對(duì)象是否存活的基本算法:給每個(gè)對(duì)象添加一個(gè)引用計(jì)數(shù)器,沒(méi)當(dāng)一個(gè)地方引用它的時(shí)候,計(jì)數(shù)器值加1;當(dāng)引用失效后,計(jì)數(shù)器值減1。但是這種方法有一個(gè)致命的缺陷,當(dāng)兩個(gè)對(duì)象相互引用時(shí)會(huì)導(dǎo)致這兩個(gè)都無(wú)法被回收。

2. 根搜索算法(目前使用中)

在主流的商用語(yǔ)言中(Java、C#…)都是使用根搜索算法來(lái)判斷對(duì)象是否存活。對(duì)于程序來(lái)說(shuō),根對(duì)象總是可以訪問(wèn)的。從這些根對(duì)象開(kāi)始,任何可以被觸及的對(duì)象都被認(rèn)為是”活著的”的對(duì)象。無(wú)法觸及的對(duì)象被認(rèn)為是垃圾,需要被回收。

Java虛擬機(jī)的根對(duì)象集合根據(jù)實(shí)現(xiàn)不同而不同,但是總會(huì)包含以下幾個(gè)方面:

  • 棧(棧幀中的本地變量表)中引用的對(duì)象。

  • 方法區(qū)中的類(lèi)靜態(tài)屬性引用的變量。

  • 方法區(qū)中的常量引用的變量。

  • 本地方法JNI的引用對(duì)象。

區(qū)分活動(dòng)對(duì)象和垃圾的兩個(gè)基本方法是引用計(jì)數(shù)和根搜索。 引用計(jì)數(shù)是通過(guò)為堆中每個(gè)對(duì)象保存一個(gè)計(jì)數(shù)來(lái)區(qū)分活動(dòng)對(duì)象和垃圾。根搜索算法實(shí)際上是追蹤從根結(jié)點(diǎn)開(kāi)始的引用圖。

在主流的商用程序語(yǔ)言(如我們的Java)的主流實(shí)現(xiàn)中,都是通過(guò)可達(dá)性分析算法來(lái)判定對(duì)象是否存活的。

二. 垃圾收集算法

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

分標(biāo)記和清除兩個(gè)階段:首先標(biāo)記處所需要回收的對(duì)象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象。

它有兩點(diǎn)不足:一個(gè)效率問(wèn)題,標(biāo)記和清除過(guò)程都效率不高;一個(gè)是空間問(wèn)題,標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片(類(lèi)似于我們電腦的磁盤(pán)碎片),空間碎片太多導(dǎo)致需要分配大對(duì)象時(shí)無(wú)法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾回收動(dòng)作。

2. 復(fù)制算法

為了解決效率問(wèn)題,出現(xiàn)了“復(fù)制”算法,他將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只需要使用其中一塊。當(dāng)一塊內(nèi)存用完了,將還存活的對(duì)象復(fù)制到另一塊上面,然后再把剛剛用完的內(nèi)存空間一次清理掉。這樣就解決了內(nèi)存碎片問(wèn)題,但是代價(jià)就是可以用內(nèi)容就縮小為原來(lái)的一半。

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

復(fù)制算法在對(duì)象存活率較高時(shí)就會(huì)進(jìn)行頻繁的復(fù)制操作,效率將降低。因此又有了標(biāo)記-整理算法,標(biāo)記過(guò)程同標(biāo)記-清除算法,但是在后續(xù)步驟不是直接對(duì)對(duì)象進(jìn)行清理,而是讓所有存活的對(duì)象都向一側(cè)移動(dòng),然后直接清理掉端邊界以外的內(nèi)存。

4. 分代收集法

當(dāng)前商業(yè)虛擬機(jī)的GC都是采用分代收集算法

為了增大垃圾收集的效率,所以JVM將堆進(jìn)行分代,分為不同的部分,一般有三部分,新生代,老年代和永久代(在新的版本中已經(jīng)將永久代廢棄,引入了元空間的概念,永久代使用的是JVM內(nèi)存而元空間直接使用物理內(nèi)存):

  • 新生代(對(duì)應(yīng)minor GC):所有新new出來(lái)的對(duì)象都會(huì)最先出現(xiàn)在新生代中,當(dāng)新生代這部分內(nèi)存滿(mǎn)了之后,就會(huì)發(fā)起一次垃圾收集事件,這種發(fā)生在新生代的垃圾收集稱(chēng)為Minor collections。這種收集通常比較快,因?yàn)樾律拇蟛糠謱?duì)象都是需要回收的,那些暫時(shí)無(wú)法回收的就會(huì)被移動(dòng)到老年代。

  • 老年代(對(duì)應(yīng)Full GC):老年代用來(lái)存儲(chǔ)那些存活時(shí)間較長(zhǎng)的對(duì)象。一般來(lái)說(shuō),我們會(huì)給新生代的對(duì)象限定一個(gè)存活的時(shí)間,當(dāng)達(dá)到這個(gè)時(shí)間還沒(méi)有被收集的時(shí)候就會(huì)被移動(dòng)到老年代中。

  • 永久代:用于存放靜態(tài)文件,如Java類(lèi)、方法等。持久代對(duì)垃圾回收沒(méi)有顯著影響,但是有些應(yīng)用可能動(dòng)態(tài)生成或者調(diào)用一些class,例如Hibernate 等,在這種時(shí)候需要設(shè)置一個(gè)比較大的持久代空間來(lái)存放這些運(yùn)行過(guò)程中新增的類(lèi)。

程序中主動(dòng)調(diào)用System.gc()強(qiáng)制執(zhí)行的GC為Full GC

大概流程:

內(nèi)存分區(qū):

年輕代(Young Generation)(Eden,Survivor-s0,Survivor-s1,大小比例默認(rèn)為8:1:1)

年老代(Old Generation)

永久代(Permanent Generation)。(包含應(yīng)用的類(lèi)/方法信息, 以及JRE庫(kù)的類(lèi)和方法信息.和垃圾回收基本無(wú)關(guān))

新生代中的對(duì)象“朝生夕死”,每次GC時(shí)都會(huì)有大量對(duì)象死去,少量存活,使用復(fù)制算法。新生代又分為Eden區(qū)和Survivor區(qū)(Survivor from、Survivor to),大小比例默認(rèn)為8:1:1。

老年代中的對(duì)象因?yàn)閷?duì)象存活率高、沒(méi)有額外空間進(jìn)行分配擔(dān)保,就使用標(biāo)記-清除或標(biāo)記-整理算法。

新產(chǎn)生的對(duì)象優(yōu)先進(jìn)去Eden區(qū),當(dāng)Eden區(qū)滿(mǎn)了之后再使用Survivor from,當(dāng)Survivor from 也滿(mǎn)了之后就進(jìn)行Minor GC(新生代GC),將Eden和Survivor from中存活的對(duì)象copy進(jìn)入Survivor to,然后清空Eden和Survivor from,這個(gè)時(shí)候原來(lái)的Survivor from成了新的Survivor to,原來(lái)的Survivor to成了新的Survivor from。復(fù)制的時(shí)候,如果Survivor to 無(wú)法容納全部存活的對(duì)象,則根據(jù)老年代的分配擔(dān)保(類(lèi)似于銀行的貸款擔(dān)保)將對(duì)象copy進(jìn)去老年代,如果老年代也無(wú)法容納,則進(jìn)行Full GC(老年代GC)。

  • 大對(duì)象直接進(jìn)入老年代:JVM中有個(gè)參數(shù)配置-XX:PretenureSizeThreshold,令大于這個(gè)設(shè)置值的對(duì)象直接進(jìn)入老年代,目的是為了避免在Eden和Survivor區(qū)之間發(fā)生大量的內(nèi)存復(fù)制。

  • 長(zhǎng)期存活的對(duì)象進(jìn)入老年代:JVM給每個(gè)對(duì)象定義一個(gè)對(duì)象年齡計(jì)數(shù)器,如果對(duì)象在Eden出生并經(jīng)過(guò)第一次Minor GC后仍然存活,并且能被Survivor容納,將被移入Survivor并且年齡設(shè)定為1。每熬過(guò)一次Minor GC,年齡就加1,當(dāng)他的年齡到一定程度(默認(rèn)為15歲,可以通過(guò)XX:MaxTenuringThreshold來(lái)設(shè)定),就會(huì)移入老年代。

  • 但是JVM并不是永遠(yuǎn)要求年齡必須達(dá)到最大年齡才會(huì)晉升老年代,如果Survivor 空間中相同年齡(如年齡為x)所有對(duì)象大小的總和大于Survivor的一半,年齡大于等于x的所有對(duì)象直接進(jìn)入老年代,無(wú)需等到最大年齡要求。

三. 垃圾收集器

垃圾回收算法是方法論,垃圾回收器是實(shí)現(xiàn)。

  • Serial收集器:串行收集器是最古老,最穩(wěn)定以及效率高的收集器,可能會(huì)產(chǎn)生較長(zhǎng)的停頓,只使用一個(gè)線程去回收。(STW)它是虛擬機(jī)運(yùn)行在client模式下的默認(rèn)新生代收集器:簡(jiǎn)單而高效(與其他收集器的單個(gè)線程相比,因?yàn)闆](méi)有線程切換的開(kāi)銷(xiāo)等)。

  • ParNew收集器:ParNew收集器其實(shí)就是Serial收集器的多線程版本。(STW)是許多運(yùn)行在Server模式下的JVM中首選的新生代收集器,其中一個(gè)很重還要的原因就是除了Serial之外,只有他能和老年代的CMS收集器配合工作。

  • Parallel Scavenge收集器:Parallel Scavenge收集器類(lèi)似ParNew收集器,Parallel收集器更關(guān)注系統(tǒng)的吞吐量(就是CPU運(yùn)行用戶(hù)代碼的時(shí)間與CPU總消耗時(shí)間的比值,即 吞吐量=運(yùn)行用戶(hù)代碼的時(shí)間/[運(yùn)行用戶(hù)代碼的時(shí)間+垃圾收集時(shí)間])。

  • CMS收集器:CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。,停頓時(shí)間短,用戶(hù)體驗(yàn)就好。

基于“標(biāo)記清除”算法,并發(fā)收集、低停頓,運(yùn)作過(guò)程復(fù)雜,分4步:

  • 初始標(biāo)記:僅僅標(biāo)記GC Roots能直接關(guān)聯(lián)到的對(duì)象,速度快,但是需要“Stop The World”

  • 并發(fā)標(biāo)記:就是進(jìn)行追蹤引用鏈的過(guò)程,可以和用戶(hù)線程并發(fā)執(zhí)行。

  • 重新標(biāo)記:修正并發(fā)標(biāo)記階段因用戶(hù)線程繼續(xù)運(yùn)行而導(dǎo)致標(biāo)記發(fā)生變化的那部分對(duì)象的標(biāo)記記錄,比初始標(biāo)記時(shí)間長(zhǎng)但遠(yuǎn)比并發(fā)標(biāo)記時(shí)間短,需要“Stop The World”

  • 并發(fā)清除:清除標(biāo)記為可以回收對(duì)象,可以和用戶(hù)線程并發(fā)執(zhí)行

由于整個(gè)過(guò)程耗時(shí)最長(zhǎng)的并發(fā)標(biāo)記和并發(fā)清除都可以和用戶(hù)線程一起工作,所以總體上來(lái)看,CMS收集器的內(nèi)存回收過(guò)程和用戶(hù)線程是并發(fā)執(zhí)行的。

CSM收集器有3個(gè)缺點(diǎn):

1)對(duì)CPU資源非常敏感

并發(fā)收集雖然不會(huì)暫停用戶(hù)線程,但因?yàn)檎加靡徊糠諧PU資源,還是會(huì)導(dǎo)致應(yīng)用程序變慢,總吞吐量降低。

CMS的默認(rèn)收集線程數(shù)量是=(CPU數(shù)量+3)/4;當(dāng)CPU數(shù)量多于4個(gè),收集線程占用的CPU資源多于25%,對(duì)用戶(hù)程序影響可能較大;不足4個(gè)時(shí),影響更大,可能無(wú)法接受。

2)無(wú)法處理浮動(dòng)垃圾(在并發(fā)清除時(shí),用戶(hù)線程新產(chǎn)生的垃圾叫浮動(dòng)垃圾),可能出現(xiàn)”Concurrent Mode Failure”失敗。

并發(fā)清除時(shí)需要預(yù)留一定的內(nèi)存空間,不能像其他收集器在老年代幾乎填滿(mǎn)再進(jìn)行收集;如果CMS預(yù)留內(nèi)存空間無(wú)法滿(mǎn)足程序需要,就會(huì)出現(xiàn)一次”Concurrent Mode Failure”失?。贿@時(shí)JVM啟用后備預(yù)案:臨時(shí)啟用Serail Old收集器,而導(dǎo)致另一次Full GC的產(chǎn)生;

3)產(chǎn)生大量?jī)?nèi)存碎片:CMS基于”標(biāo)記-清除”算法,清除后不進(jìn)行壓縮操作產(chǎn)生大量不連續(xù)的內(nèi)存碎片,這樣會(huì)導(dǎo)致分配大內(nèi)存對(duì)象時(shí),無(wú)法找到足夠的連續(xù)內(nèi)存,從而需要提前觸發(fā)另一次Full GC動(dòng)作。

  • Serial Old收集器:Serial 收集器的老年代版本,單線程,“標(biāo)記整理”算法,主要是給Client模式下的虛擬機(jī)使用??梢宰鳛镃MS的后背方案,在CMS發(fā)生Concurrent Mode Failure是使用

  • Parallel Old 收集器:Parallel Scavenge的老年代版本,多線程,“標(biāo)記整理”算法,JDK 1.6才出現(xiàn)。在此之前Parallel Scavenge只能同Serial Old搭配使用,由于Serial Old的性能較差導(dǎo)致Parallel Scavenge的優(yōu)勢(shì)發(fā)揮不出來(lái),Parallel Old收集器的出現(xiàn),使“吞吐量?jī)?yōu)先”收集器終于有了名副其實(shí)的組合。在吞吐量和CPU敏感的場(chǎng)合,都可以使用Parallel Scavenge/Parallel Old組合。

  • G1收集器:G1 (Garbage-First)是一款面向服務(wù)器的垃圾收集器,主要針對(duì)配備多顆處理器及大容量?jī)?nèi)存的機(jī)器. 以極高概率滿(mǎn)足GC停頓時(shí)間要求的同時(shí),還具備高吞吐量性能特征。G1是面向服務(wù)端應(yīng)用的垃圾收集器。它的使命是未來(lái)可以替換掉CMS收集器。

關(guān)于怎么理解Java常見(jiàn)知識(shí)點(diǎn)中的垃圾回收機(jī)制就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI