Java基礎(chǔ)之垃圾回收機(jī)制詳解
一、GC的作用
進(jìn)行內(nèi)存管理
C語言中的內(nèi)存,申請(qǐng)內(nèi)存之后需要手動(dòng)釋放;一旦忘記釋放,就會(huì)發(fā)生內(nèi)存泄漏!
而Java語言中,申請(qǐng)內(nèi)存后會(huì)由GC來釋放內(nèi)存空間,無需手動(dòng)釋放
GC雖然代替了手動(dòng)釋放的操作,但是它也有局限性:
- 需要消耗更多的資源;
- 沒有手動(dòng)釋放那么及時(shí);
- STW(Stop The World)會(huì)影響程序的執(zhí)行效率
二、GC主要回收哪些內(nèi)存
(1)堆:主要回收堆中的內(nèi)存
(2)方法區(qū):需要回收
(3)棧(包括本地方法棧和JVM虛擬機(jī)棧):不需要回收,棧上的內(nèi)存什么時(shí)候釋放是明確的(線程結(jié)束,棧上的內(nèi)存也就被釋放了;對(duì)應(yīng)的某個(gè)棧幀銷毀[某個(gè)方法執(zhí)行完畢],也會(huì)導(dǎo)致對(duì)應(yīng)的局部變量被釋放)
(4)程序計(jì)數(shù)器:不需要被回收
GC回收內(nèi)存的基本單位:對(duì)象
GC回收對(duì)象的基本思路
(1)標(biāo)記:判斷當(dāng)前對(duì)象的生死,對(duì)象不再被使用為死,則需要回收,反之不需要被回收;
標(biāo)記的方法:
- 引用計(jì)數(shù)法
記錄當(dāng)前這個(gè)對(duì)象是否有引用指向,有則引用計(jì)數(shù)加1,如果當(dāng)前這個(gè)對(duì)象的引用指向了其他新的對(duì)象,則引用計(jì)數(shù)減1,當(dāng)引用計(jì)數(shù)為0的時(shí)候,我們認(rèn)為這個(gè)對(duì)象需要被回收!
缺點(diǎn):無法解決循環(huán)引用問題
下面用一段偽代碼來演示一下循環(huán)引用問題:
class Test{
Test t = null;
}
Test a = new Test();
Test b = new Test();
a.t = b;
b.t = a;
a = null;
b = null;
我們發(fā)現(xiàn),在上述代碼中已經(jīng)沒有辦法使用對(duì)象a和對(duì)象b了,但是它們的引用計(jì)數(shù)不為1.想使用對(duì)象a,就得找到對(duì)象a的引用,但是對(duì)象a的引用又在對(duì)象b當(dāng)中。想使用對(duì)象b,就得找到對(duì)象b 的引用,但是對(duì)象b的引用又在對(duì)象a當(dāng)中。

- 可達(dá)性分析:
代碼中的對(duì)象具有一定的關(guān)聯(lián)關(guān)系,這樣錯(cuò)綜復(fù)雜的關(guān)系,構(gòu)成了一個(gè)"有向圖"??蛇_(dá)性分析也就是遍歷這個(gè)對(duì)象關(guān)系的“有向圖”。如果某個(gè)對(duì)象可以被遍歷到,那么它就是可達(dá)的(非垃圾),那么就是不可達(dá)的(是垃圾)
那么可達(dá)性分析從哪里開始呢?
a)針對(duì)每個(gè)線程的每個(gè)棧幀的局部變量表(線程有很多,每個(gè)線程棧幀也有很多,每個(gè)棧幀也會(huì)有很多個(gè)變量);
b)常量池中引用的對(duì)象;
c)方法區(qū)中靜態(tài)變量引用的對(duì)象;
因?yàn)楸闅v的起點(diǎn)不止一個(gè),而是很多個(gè)起點(diǎn),因此把這些起點(diǎn)也稱之為GCRoot
- 回收方法區(qū)對(duì)象的規(guī)則:
a)該類的所有實(shí)例已經(jīng)被回收;
b)加載類的ClassLoader也已經(jīng)被回收了;
c)該類對(duì)象沒有在代碼中使用了
同時(shí)具備以上三個(gè)條件,就認(rèn)為該類對(duì)象是可以被回收的
回收的方法:
- 標(biāo)記-清除【適合老年代】

通過上面的圖,我們可以發(fā)現(xiàn),兩個(gè)空閑區(qū)被其他的對(duì)象分隔開了。一旦需要一個(gè)比較大的空間,就會(huì)申請(qǐng)失敗。
標(biāo)記-清除法的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):簡(jiǎn)單高效
缺點(diǎn):會(huì)出現(xiàn)內(nèi)存碎片
- 標(biāo)記-復(fù)制【適合新生代】

優(yōu)點(diǎn):解決了內(nèi)存碎片問題,保證回收之后不會(huì)存在碎片(回收后使用的對(duì)象之間是連續(xù)的,空余內(nèi)存之間也是連續(xù)的)
缺點(diǎn):需要一塊額外的空間,如果生存的對(duì)象較多就比較難低效
- 標(biāo)記-整理【適合老年代】

優(yōu)點(diǎn):沒有內(nèi)存碎片問題,也不需要額外的空間
缺點(diǎn):類似于順序表的刪除操作,效率不是很高
三、分代回收
按照對(duì)象的年齡,將堆內(nèi)存分為:新生代(伊甸區(qū)和生存區(qū))、老年代
對(duì)象的年齡不是直接使用時(shí)間來記錄,而是使用對(duì)象活過GC輪次來記錄(GC是按照一定周期來運(yùn)行)

一個(gè)對(duì)象的一生:
(1)對(duì)象誕生于新生代的伊甸區(qū)。新產(chǎn)生的對(duì)象的內(nèi)存就是新生代中的內(nèi)存
(2)第一輪GC掃描伊甸區(qū)之后,就會(huì)把大量的對(duì)象回收掉。少數(shù)沒有被回收的對(duì)象,就會(huì)通過標(biāo)記-復(fù)制算法進(jìn)入到生存區(qū)
(3)少數(shù)進(jìn)入生存區(qū)的對(duì)象,再次被GC掃描(對(duì)這些對(duì)象進(jìn)行可達(dá)性分析)。如果發(fā)現(xiàn)該對(duì)象已經(jīng)不可達(dá),也就被銷毀了。沒有被銷毀的對(duì)象,再次通過標(biāo)記-復(fù)制算法,把它拷貝到另一個(gè)生存區(qū)。
(4)對(duì)象在兩個(gè)生存區(qū)中經(jīng)過若干次拷貝,如果還沒有被回收,那么就說明這些個(gè)對(duì)象存活時(shí)間比較久,就拷貝到老年代
(5)老年代的對(duì)象也是要經(jīng)過GC掃描的。由于老年代的對(duì)象生存時(shí)間比較長(zhǎng)。因此掃描周期要比新生代的周期要長(zhǎng)
相關(guān)術(shù)語:
- Partical GC:只進(jìn)行一部分內(nèi)存區(qū)域的GC
- Full GC:針對(duì)整個(gè)內(nèi)存區(qū)域進(jìn)行GC
- Minor GC:針對(duì)新生代內(nèi)存的GC,執(zhí)行頻繁,速度較快
- Major GC:針對(duì)老年代的GC,沒那么頻繁。速度較慢,通常由Minor GC 觸發(fā)
四、垃圾回收器

垃圾回收器做的兩件事情:標(biāo)記(可達(dá)性分析)+回收(標(biāo)記清除,標(biāo)記復(fù)制,標(biāo)記整理)
- Serial收集器(給新生代使用,串行回收)【存在STW】

采用復(fù)制算法,單線程進(jìn)行標(biāo)記和回收
- ParNew收集器(新生代收集器,多線程GC)

采用復(fù)制算法,多線程進(jìn)行標(biāo)記和回收
- Parallel scavenge收集器(新生代收集器,并行GC)
設(shè)計(jì)初衷是為了縮短STW時(shí)間,以犧牲吞吐量和新生代空間作為代價(jià)。
相當(dāng)于承諾用戶,在一定時(shí)間內(nèi)就會(huì)完成一次GC。
- Serial Old收集器(老年代收集器,串行GC)

- Parallel old收集器(老年代收集器,并行GC)

使用多線程完成標(biāo)記整理,效率更高,消耗的CPU資源更多
- CMS垃圾回收器(老年代收集器,并行GC,采用多線程標(biāo)記清除算法)
a)初始標(biāo)記【STW】
只是把和GCRoot相關(guān)的對(duì)象標(biāo)記出來,涉及STW
b)并發(fā)標(biāo)記
執(zhí)行整個(gè)標(biāo)記遍歷的過程(從GCRoot開始,把能訪問的對(duì)象都遍歷)
不需要暫停用戶線程
消耗的時(shí)間相對(duì)比較久,但是可以和用戶線程并發(fā)
注意:當(dāng)進(jìn)行并發(fā)標(biāo)記的時(shí)候,當(dāng)用戶線程也在執(zhí)行,可能導(dǎo)致某個(gè)對(duì)象,剛剛標(biāo)記的時(shí)候不是垃圾,代碼執(zhí)行后,就成了垃圾
c)重新標(biāo)記(CMS remark)【STW】
修正誤差
d)并發(fā)清除
多線程的方式將剛剛的垃圾對(duì)象都清除釋放掉,可以和應(yīng)用程序并發(fā)執(zhí)行
優(yōu)點(diǎn):能夠讓STW時(shí)間盡量短
缺點(diǎn):有內(nèi)存碎片; GC操作和應(yīng)用程序并發(fā)進(jìn)行,消耗CPU資源多;
- G1回收器(Java11開始默認(rèn)使用)
既可以回收新生代,也可以回收老年代

每個(gè)矩形稱為一個(gè)region
E表示伊甸區(qū)
S表示生存區(qū)
T表示老年代
H表示存放大對(duì)象的區(qū)域
以region為單位進(jìn)行回收,回收粒度更精細(xì)
針對(duì)新生區(qū)的region同樣適用復(fù)制算法
針對(duì)老年代的回收類似于CMS
a)初始標(biāo)記【STW】:只去找和GRoot直接相連的對(duì)象
b)并發(fā)標(biāo)記:和應(yīng)用程序并發(fā)執(zhí)行,進(jìn)行可達(dá)性分析,遍歷所有對(duì)象。如果發(fā)現(xiàn)某個(gè)老年代region中已經(jīng)沒有存活對(duì)象,就直接回收
c)最終標(biāo)記:修正第二步產(chǎn)生的誤差
d)篩選回收:挑選出對(duì)象存活率低的region進(jìn)行回收
五、總結(jié)

到此這篇關(guān)于Java基礎(chǔ)之垃圾回收機(jī)制詳解的文章就介紹到這了,更多相關(guān)Java垃圾回收機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java Calendar類常用示例_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
從JDK1.1版本開始,在處理日期和時(shí)間時(shí),系統(tǒng)推薦使用Calendar類進(jìn)行實(shí)現(xiàn)。接下來通過實(shí)例代碼給大家詳細(xì)介紹Java Calendar類相關(guān)知識(shí),需要的朋友參考下吧2017-04-04
如何使用mybatis-plus實(shí)現(xiàn)分頁查詢功能
最近在研究mybatis,然后就去找簡(jiǎn)化mybatis開發(fā)的工具,發(fā)現(xiàn)就有通用Mapper和mybatis-plus兩個(gè)比較好的可是使用,可是經(jīng)過對(duì)比發(fā)現(xiàn)還是mybatis-plus比較好,下面這篇文章主要給大家介紹了關(guān)于如何使用mybatis-plus實(shí)現(xiàn)分頁查詢功能的相關(guān)資料,需要的朋友可以參考下2022-06-06
關(guān)于dubbo的RPC和RESTful性能及對(duì)比
這篇文章主要介紹了關(guān)于dubbo的RPC和RESTful性能及對(duì)比,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12
Java工作環(huán)境的配置與Eclipse的安裝過程
這篇文章主要介紹了Java工作環(huán)境的配置與Eclipse的安裝過程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02
淺談Java中的interface應(yīng)用與面向接口編程
這篇文章主要介紹了淺談Java中的interface應(yīng)用與面向接口編程,Java的關(guān)鍵字interface應(yīng)用,一個(gè)接口,多個(gè)實(shí)現(xiàn)類,面向接口編程,把業(yè)務(wù)邏輯線提取出來作為接口,具體的業(yè)務(wù)實(shí)現(xiàn)通過該接口的實(shí)現(xiàn)類來完成,需要的朋友可以參考下2023-10-10

