溫馨提示×

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

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

Java垃圾回收機(jī)制的底層原理是什么

發(fā)布時(shí)間:2021-08-03 11:27:54 來(lái)源:億速云 閱讀:111 作者:Leah 欄目:大數(shù)據(jù)

這篇文章給大家介紹Java垃圾回收機(jī)制的底層原理是什么,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。

3.1.2 垃圾回收機(jī)制

Java 內(nèi)存運(yùn)行時(shí)區(qū)域中的程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧隨線程而生滅,棧中的棧幀隨著方法的進(jìn)入和退出而有條不紊地執(zhí)行著出棧和入棧操作。每一個(gè)棧幀中分配多少內(nèi)存基本上是在類結(jié)構(gòu)確定下來(lái)時(shí)就已知的(盡管在運(yùn)行期會(huì)由 JIT 編譯器進(jìn)行一些優(yōu)化),因此這幾個(gè)區(qū)域的內(nèi)存分配和回收都具備確定性,不需要過(guò)多考慮回收的問(wèn)題,因?yàn)榉椒ńY(jié)束或者線程結(jié)束時(shí),內(nèi)存自然就跟隨著回收了。

而 Java 堆不一樣,一個(gè)接口中的多個(gè)實(shí)現(xiàn)類需要的內(nèi)存可能不一樣,一個(gè)方法中的多個(gè)分支需要的內(nèi)存也可能不一樣,我們只有在程序處于運(yùn)行期間時(shí)才能知道會(huì)創(chuàng)建哪些對(duì)象,這部分內(nèi)存的分配和回收都是動(dòng)態(tài)的,垃圾收集器所關(guān)注的是這部分內(nèi)存。

自動(dòng)垃圾收集

自動(dòng)垃圾收集是查看堆內(nèi)存,識(shí)別正在使用那些對(duì)象,那些對(duì)象未被刪除,刪除未被使用對(duì)象的過(guò)程。使用中對(duì)象或引用的對(duì)象意味著程序的某些部分任然維護(hù)指向該對(duì)象的指針。程序的任何部分都不再引用未使用的對(duì)象或未引用的對(duì)象,則可以回收未引用對(duì)象的內(nèi)存。在C語(yǔ)言中,內(nèi)存的分配和釋放是手動(dòng)的過(guò)程,在Java語(yǔ)言中內(nèi)存的分配和回收是由垃圾收集器自動(dòng)處理的。

而在自動(dòng)垃圾收集中如何確定那些內(nèi)存需要被回收,通常來(lái)說(shuō)第一步就是標(biāo)記,利用引用計(jì)數(shù),可達(dá)性分析等來(lái)標(biāo)記那些內(nèi)存正在使用,那些內(nèi)存不在使用。

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

引用計(jì)數(shù)法主要是通過(guò)給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)一個(gè)地方引用它時(shí),計(jì)數(shù)器的值就加1,當(dāng)引用失效時(shí),計(jì)數(shù)器的值就減1,當(dāng)計(jì)數(shù)器的值為0時(shí),對(duì)象就是不可能被使用的,可以對(duì)其進(jìn)行垃圾回收。引用計(jì)數(shù)法的實(shí)現(xiàn)簡(jiǎn)單,效率也比較高,但是它很難解決對(duì)象之間循環(huán)引用的問(wèn)題。

可達(dá)性分析算法

可達(dá)性分析算法,也稱為根搜索算法,這個(gè)算法的基本思路就是通過(guò)一系列的名為“GC Roots”的對(duì)象為起始點(diǎn),從這些節(jié)點(diǎn)開(kāi)始向下搜索,搜索所走過(guò)的路徑稱為引用鏈,當(dāng)一個(gè)對(duì)象到“GC Roots”沒(méi)有任何引用鏈相連,則證明對(duì)象是不可用的,可以對(duì)其進(jìn)行垃圾回收。

簡(jiǎn)單來(lái)說(shuō),將對(duì)象及其引用關(guān)系看作一個(gè)圖,選定活動(dòng)的對(duì)象作為“GC Roots”,然后跟蹤引用鏈條,如果一個(gè)對(duì)象和”GC Roots“之間不可達(dá),也就是不存在引用,即可認(rèn)為是可回收對(duì)象。

引用類型和可達(dá)性級(jí)別

引用類型

  • 強(qiáng)引用(Strong Reference):最常見(jiàn)的普通引用,只要還有強(qiáng)引用指向一個(gè)對(duì)象,就不會(huì)回收

  • 軟引用(Soft Reference):JVM認(rèn)為內(nèi)存不足阿時(shí)候,才會(huì)去試圖回收軟引用指向的對(duì)象(緩存場(chǎng)景)

  • 弱引用(Weak Reference):雖然是引用,但隨時(shí)有可能被回收掉

  • 虛引用(Phantom Reference):不能通過(guò)它訪問(wèn)對(duì)象,提供對(duì)象被finalize以后,執(zhí)行指定邏輯的機(jī)制。

可達(dá)性級(jí)別

  • 強(qiáng)可達(dá)(Strongly Reachable):一個(gè)對(duì)象可以有一個(gè)或多個(gè)線程可以不通過(guò)各種引用訪問(wèn)到的情況

  • 軟可達(dá)(Softly Reachable):只能通過(guò)軟引用才能訪問(wèn)到的狀態(tài)

  • 弱引用(Weakly Reachable):只能通過(guò)弱引用訪問(wèn)時(shí)的狀態(tài),當(dāng)弱引用被清除的時(shí)候,就符合銷毀條件

  • 幻像可達(dá)(Phantom Reachable):不存在其他引用,并且finalize過(guò)了,只有幻像引用指向這個(gè)對(duì)象

  • 不可達(dá)(Unreachable):意味著對(duì)象可以被清除了

垃圾收集算法

目前主流廣泛使用的垃圾收集算法,主要有標(biāo)記-清除算法(Mark-Sweep),復(fù)制算法(Coping)以及標(biāo)記-整理算法(Mark-Compact)。

標(biāo)記-清除算法:首先識(shí)別出所有要回收的對(duì)象,然后進(jìn)行清除。標(biāo)記,清除的過(guò)程效率有限,存在內(nèi)存碎片化的問(wèn)題,不適合特別大的堆。其他收集算法基本都是基于標(biāo)記-清除的思路進(jìn)行改進(jìn)。

復(fù)制算法:劃分兩塊同等大小的區(qū)域,收集時(shí)將活著的對(duì)象復(fù)制到另一塊區(qū)域。復(fù)制過(guò)程中將對(duì)象順序放置,就可以避免內(nèi)存碎片化。但是復(fù)制加上預(yù)留內(nèi)存有一定的浪費(fèi)

標(biāo)記-整理算法:類似于標(biāo)記-清除,但為避免內(nèi)存碎片化,它會(huì)在清理過(guò)程中將對(duì)象移動(dòng),以確保移動(dòng)后的對(duì)象占用連續(xù)的內(nèi)存

分代收集

根據(jù)對(duì)象的存活周期,將內(nèi)存劃分為幾個(gè)區(qū)域,不同區(qū)域采用合適的垃圾收集算法。目前主流的JVM一般將堆內(nèi)存分為新生代和老年代(大小比列為1:2),而新生代又被分為了eden、from survivor、to survivor(大小比列為8:1:1)。在新生代中,每次垃圾收集時(shí)都發(fā)現(xiàn)有大量對(duì)象死去,只有少量存活,那么就可以選用復(fù)制算法,只需付出少量對(duì)象的復(fù)制成本就可以完成收集。在老年代中,對(duì)象存活率高,就可以使用標(biāo)記-清除,標(biāo)記-整理算法。

新生代幾乎是所有JAVA對(duì)象出生的地方,JAVA對(duì)象申請(qǐng)的內(nèi)存和存放都是在這個(gè)地方,JVM每次只會(huì)使用新生代中的eden和其中一塊survivor來(lái)為對(duì)象服務(wù),所以無(wú)論什么時(shí)候,都會(huì)有一塊survivor空間空閑。當(dāng)對(duì)象經(jīng)過(guò)一次minor gc后仍然存活,并且能夠被另外一塊survivor所容納,則使用復(fù)制算法將這些仍然存活的對(duì)象復(fù)制到另外一塊survior區(qū)域中,然后清理掉eden和之前使用的survivor區(qū)域,并將這些存活的對(duì)象年齡+1,以后對(duì)象在survivor中每熬過(guò)一次minor gc則年齡增加1,當(dāng)年齡達(dá)到某個(gè)值時(shí)(默認(rèn)15,通過(guò)設(shè)置參數(shù)-XX:MaxTenuringThreshold來(lái)設(shè)置),這些對(duì)象就會(huì)進(jìn)入老年代。當(dāng)然,對(duì)于一些較大的對(duì)象可以直接進(jìn)入老年代,可以根據(jù)-XX:+PretenureSizeThreshold設(shè)置大對(duì)象進(jìn)入老年代的閾值。

垃圾收集器

垃圾收集算法只是內(nèi)存回收的理論方法,垃圾收集器才是內(nèi)存回收的具體實(shí)現(xiàn)。Java虛擬機(jī)規(guī)范中對(duì)垃圾收集器的實(shí)現(xiàn)沒(méi)有具體的規(guī)定,不同廠商,不同版本的垃圾收集器可能會(huì)差別很大。目前常見(jiàn)的垃圾收集器主要有Serial收集器,ParNew收集器,Parallel Scavenge收集器,Serial Old收集器,Parallel Old收集器,CMS收集器,G1收集器。這些垃圾收集器各有優(yōu)劣,一般都是組合在一起使用,下圖展示了在新生代和老年代可用的垃圾收集器組合:

Serial收集器

Serial收集器是一個(gè)串行收集器,使用單個(gè)線程來(lái)執(zhí)行所有垃圾收集工作,適合單處理器機(jī)器,GC在工作的時(shí)候?qū)和F渌械墓ぷ骶€程,即”Stop The World“。Serial收集器是一個(gè)新生代的單線程收集器,它使用復(fù)制算法,是虛擬機(jī)在client模式下的默認(rèn)新生代收集器,可以通過(guò)設(shè)置參數(shù)-XX:+UseSerialGC來(lái)使用。

ParNew收集器

ParNew收集器可以理解為串行收集器的多線程版本,其整體算法和Serial比較相似,除了使用多線程進(jìn)行垃圾回收,其他的基本和Serial收集器一樣。ParNew收集器是虛擬機(jī)在server模式下的默認(rèn)新生代收集器,可以通過(guò)設(shè)置參數(shù)-XX:+UseParNewGC來(lái)使用。默認(rèn)情況下它開(kāi)啟的線程數(shù)和CPU數(shù)量相同,也可以通過(guò)設(shè)置參數(shù)-XX:ParallelGCThreads來(lái)配置垃圾收集的線程數(shù)。

Parallel Scavenge收集器

Parallel Scavenge收集器也是一個(gè)新生代的并行垃圾收集器,使用的也是復(fù)制算法。Parallel Scavenge收集器的目標(biāo)是達(dá)到一個(gè)可控制的吞吐量。吞吐量就是CPU運(yùn)行用戶代碼的時(shí)間與CPU消耗的總時(shí)間的比值,即吞吐量=運(yùn)行用戶代碼時(shí)間/(運(yùn)行用戶代碼時(shí)間+垃圾收集時(shí)間)。Parallel Scavenge收集器提供-XX:MaxGCPauseMillis來(lái)控制最大垃圾收集停頓時(shí)間和-XX:GCTimeRatio來(lái)直接設(shè)置吞吐量的大小。

Serial Old收集器

Serial Old收集器是Serial收集器的在老年代使用的版本,它采用了標(biāo)記-整理算法,可以通過(guò)設(shè)置參數(shù)-XX:+UseSerialOldGC來(lái)使用。

Parallel Old收集器

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多線程和標(biāo)記-整理算法,可以通過(guò)設(shè)置參數(shù)-XX:+UseParallelOldGC來(lái)使用。

CMS收集器

CMS收集器是一個(gè)并發(fā)收集器,CMS即Concurrent Mark Swap,主要用于老年代,使用標(biāo)記-清除算法,可以通過(guò)設(shè)置參數(shù)-XX:+UseConcMarkSweepGC來(lái)使用。CMS收集器可以并行的執(zhí)行用戶程序和垃圾回收,這樣就可以減少回收的停頓時(shí)間。CMS收集器設(shè)計(jì)的主要目的就是為了減少回收停頓時(shí)間,目前很多的Java應(yīng)用都集中在互聯(lián)網(wǎng)網(wǎng)站或B/S系統(tǒng)服務(wù)端,這類應(yīng)用尤其重視服務(wù)的響應(yīng)速度,希望系統(tǒng)停頓時(shí)間最短,CMS收集器非常符合這類應(yīng)用的要求。當(dāng)然,CMS收集器也有自己的缺點(diǎn),它會(huì)占用更多的CPU資源,并和用戶線程爭(zhēng)搶,同時(shí)由于采用標(biāo)記-清除算法,存在著內(nèi)存碎片化的問(wèn)題,長(zhǎng)時(shí)間運(yùn)行等情況下可能發(fā)full gc導(dǎo)致惡劣的停頓。

CMS收集器垃圾收集的整個(gè)過(guò)程有4個(gè)步驟,初始標(biāo)記,并發(fā)標(biāo)記,重寫標(biāo)記以及并發(fā)清除。其中初始標(biāo)記和重寫標(biāo)記任然需要”Stop The World”,但是速度很快,整個(gè)過(guò)程中耗時(shí)的操作在并發(fā)標(biāo)記和并發(fā)清除階段,在這個(gè)過(guò)程中,收集器線程都可以和用戶線程一起工作。

G1收集器

G1(Garage First)收集器是當(dāng)前垃圾收集器技術(shù)最前沿的成果,在JDK7中開(kāi)始使用(可以通過(guò)配置-XX:+UseG1GC使用),從JDK9開(kāi)始G1已經(jīng)成為JVM默認(rèn)的垃圾收集器。G1垃圾收集器也是以關(guān)注延遲為目標(biāo)、服務(wù)器端應(yīng)用的垃圾收集器,其目標(biāo)就是取代CMS垃圾收集器。

G1采用了分區(qū)(Region)的思路,將整個(gè)堆空間分成若干個(gè)大小相等的內(nèi)存區(qū)域,每次分配對(duì)象空間將逐段地使用內(nèi)存,這樣最大的好處就是化整為零,避免全內(nèi)存掃描,只需要按照區(qū)域來(lái)進(jìn)行掃描即可。啟動(dòng)時(shí)可以通過(guò)參數(shù)-XX:G1HeapRegionSize=n可指定分區(qū)大小(1MB~32MB,且必須是2的冪),默認(rèn)將整堆劃分為2048個(gè)分區(qū)。在堆的使用上,整個(gè)內(nèi)存分區(qū)不存在物理上的年輕代和老年代的區(qū)別,也不需要完全獨(dú)立的 Survivor to space 堆做復(fù)制準(zhǔn)備。G1 只有邏輯上的分代概念,或者說(shuō)每個(gè)分區(qū)都可能隨 G1 的運(yùn)行在不同代之間前后切換。

G1收集器整體采用標(biāo)記-整理算法,局部是通過(guò)是通過(guò)復(fù)制算法,不會(huì)產(chǎn)生內(nèi)存碎片,能充分利用多 CPU、多核環(huán)境硬件優(yōu)勢(shì),盡量縮短”Stop The World”的時(shí)間

下圖左側(cè)為G1收集器的內(nèi)存劃分概圖,紅色為新生代(沒(méi)有S的為eden區(qū)域,有S的為survivor區(qū)域),藍(lán)色為老年代(帶有H的表示humongous ,存放humongous objects,即大于等于region一半的對(duì)象)。右側(cè)為G1收集器的垃圾收集環(huán)圖,G1的垃圾收集在層次上可以分為兩個(gè)階段,young-only階段和Space-reclamation階段,在young-only階段又分為Initial Mark ,Remark,Cleanup。

關(guān)于Java垃圾回收機(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