# 前言
初級(jí) Java 程序員步入中級(jí)程序員的有一個(gè)無(wú)法繞過(guò)的階段------GC(Garbage Collection)。作為 Java 程序員蒋譬,說(shuō)實(shí)話,很幸福号胚,不用像 C 程序員那樣,時(shí)刻關(guān)心著內(nèi)存浸遗,就像網(wǎng)上有句名言------生活從來(lái)都不容易猫胁,只不過(guò)是有人替你負(fù)重前行!是的跛锌,GC 在替我們做這些臟活累活弃秆,GC 像讓我們把精力都放在業(yè)務(wù)上,而不用每時(shí)每刻都在想著內(nèi)存∷杳保現(xiàn)在菠赚,GC 也是每個(gè)語(yǔ)言的標(biāo)準(zhǔn)配置了。不然誰(shuí)會(huì)去使用這個(gè)語(yǔ)言呢氢卡?
然而锈至,作為一個(gè)合格的程序員,對(duì)底層的好奇是進(jìn)步的動(dòng)力译秦,如果一個(gè)程序員失去了好奇心峡捡,那就可以說(shuō)他在程序員這條道路上就結(jié)束了。
難道我們不好奇 GC 到底是怎么做的嗎筑悴?接下來(lái)们拙,我們就分析 GC 做了哪些事情。
實(shí)際上阁吝,GC 主要做3件事情:
- 哪些內(nèi)存需要回收砚婆?
- 什么時(shí)候回收?
- 如何回收突勇?
說(shuō)到底装盯,GC 就是做這3件事情,如果你能解決這3個(gè)問(wèn)題甲馋,那么你也可以實(shí)現(xiàn)一個(gè) GC埂奈。
那我們就一個(gè)一個(gè)問(wèn)題來(lái)看看。
1. 哪些內(nèi)存需要回收
還記得我們之前分享的關(guān)于 JVM 運(yùn)行時(shí)數(shù)據(jù)區(qū)嗎定躏?有堆账磺,有棧,有方法區(qū)(永久代)痊远,還有直接內(nèi)存垮抗,還有 PC 寄存器。其中碧聪,GC 的主要戰(zhàn)場(chǎng)就是堆冒版,當(dāng)然,方法區(qū)也是需要 GC 的矾削。但重點(diǎn)還是堆壤玫。
我們知道豁护,堆中內(nèi)存是共享的哼凯,基本所有的對(duì)象都是在堆中創(chuàng)建欲间。當(dāng)一個(gè)對(duì)象不需要使用了,理論上我們就需要釋放他所占用的內(nèi)存断部。
問(wèn)題來(lái)了猎贴,如何分辨一個(gè)對(duì)象不需要使用了呢?答案是:不可能被任何途徑使用的對(duì)象蝴光。也就是說(shuō)他沒(méi)有了任何引用她渴。我們知道,引用在棧中蔑祟,實(shí)例在堆中趁耗,當(dāng)一個(gè)實(shí)例沒(méi)有了指向他的引用,我們認(rèn)為疆虚,這個(gè)實(shí)例就需要清除并釋放他所占用的內(nèi)存了苛败。
那么 GC 是如何實(shí)現(xiàn)的呢?一般而言有2種方法:
- 引用計(jì)數(shù)法(有缺陷径簿,無(wú)法解決循環(huán)引用問(wèn)題罢屈,JVM 沒(méi)有采用)
- 可達(dá)性分析(解決了引用計(jì)數(shù)的缺陷,被 JVM 采用)
什么是引用計(jì)數(shù)法呢篇亭?
給對(duì)象中添加一個(gè)引用計(jì)數(shù)器缠捌,每當(dāng)有一個(gè)地方引用他是,計(jì)數(shù)器值就加1译蒂;當(dāng)引用失效時(shí)曼月,計(jì)數(shù)器值就減1;任何時(shí)刻計(jì)數(shù)器為0的對(duì)象就是不可能在被使用的柔昼。
雖然乍看這個(gè)算法簡(jiǎn)單哑芹,效率也高,但有一個(gè)問(wèn)題這個(gè)算法無(wú)法解決岳锁,就是循環(huán)引用绩衷。試想一下:A 對(duì)象引用了 B,B 對(duì)象也引用了 A激率,但 A 和 B 都不被別的地方使用咳燕,也就是說(shuō),實(shí)際上這兩個(gè)對(duì)象是垃圾對(duì)象乒躺,但是由于他們互相持有引用招盲,導(dǎo)致他們的引用計(jì)數(shù)器都不為0,因此系統(tǒng)無(wú)法判斷是垃圾嘉冒,也無(wú)法回收他們曹货。
所以咆繁,在現(xiàn)在的 JVM 中,是沒(méi)有使用這個(gè)算法的顶籽。我們知道就行玩般。
引用計(jì)數(shù)法不行,那就再說(shuō)說(shuō)可達(dá)性分析算法礼饱。
這個(gè)算法的基本思想就是通過(guò)一系列的稱為 “GC Roots” 的對(duì)象作為起始點(diǎn)坏为,從這些節(jié)點(diǎn)開(kāi)始向下搜索,所有所走過(guò)的路徑稱為引用鏈(Reference Chain)镊绪,當(dāng)一個(gè)對(duì)象到 GC Roots 沒(méi)有任何引用鏈相連(也就是對(duì)象不可達(dá))時(shí)匀伏,則證明此對(duì)象是不可用的。如下圖所示蝴韭,obj5 , obj6, obj7 雖然互相有關(guān)聯(lián)够颠,但是他們到 GC Roots 是不可達(dá)的,所以他們將會(huì)判定為是可回收的對(duì)象榄鉴。
那么哪些對(duì)象可以作為 GC Roots 對(duì)象呢履磨?
- 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象。
- 方法區(qū)中類靜態(tài)屬性引用的對(duì)象牢硅。
- 方法區(qū)中常量引用的對(duì)象蹬耘。
- 本地方法棧中 JNI (即 native 方法)引用的對(duì)象。
2. 什么時(shí)候回收减余?
注意:即使是在可達(dá)性分析算法中不可達(dá)的對(duì)象综苔,也并非是"非死不可的",這時(shí)候他們實(shí)際上是處于 “緩刑” 階段位岔。因?yàn)橐嬲嬉粋€(gè)對(duì)象的死亡如筛,至少需要經(jīng)歷兩次標(biāo)記過(guò)程:
如果對(duì)象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒(méi)有與 GC Roots 相連接的引用鏈,那他將會(huì)被第一次標(biāo)記并且進(jìn)行一次篩選抒抬,篩選的條件是此對(duì)象是否有必要執(zhí)行 finalize 方法杨刨。注意:當(dāng)對(duì)象沒(méi)有覆蓋 finalize 方法,或者 finalize 方法已經(jīng)被虛擬機(jī)調(diào)用過(guò)擦剑,虛擬機(jī)將這兩種情況都視為 “沒(méi)有必要執(zhí)行”妖胀。也就是說(shuō),finalize 方法只會(huì)被執(zhí)行一次惠勒。
=========================================================
如果這個(gè)對(duì)象被判定為有必要執(zhí)行 finalize 方法赚抡,那么這個(gè)對(duì)象將會(huì)放置在一個(gè)叫做 F-Queue 的隊(duì)列之中,并在稍后由一個(gè)虛擬機(jī)自動(dòng)建立的纠屋,低優(yōu)先級(jí)的 Finalizer 線程去執(zhí)行它涂臣。注意:如果一個(gè)對(duì)象在 finalize 方法中運(yùn)行緩慢,將會(huì)導(dǎo)致隊(duì)列后的其他對(duì)象永遠(yuǎn)等待售担,嚴(yán)重時(shí)將會(huì)導(dǎo)致系統(tǒng)崩潰赁遗。
=========================================================
finalize 方法是對(duì)象逃脫死亡命運(yùn)的最后一道關(guān)卡署辉。稍后 GC 將對(duì)隊(duì)列中的對(duì)象進(jìn)行第二次規(guī)模的標(biāo)記,如果對(duì)象要在 finalize 中 “拯救” 自己岩四,只需要將自己關(guān)聯(lián)到引用上即可哭尝,通常是 this。如果這個(gè)對(duì)象關(guān)聯(lián)上了引用炫乓,那么在第二次標(biāo)記的時(shí)候他將被移除出 “即將回收” 的集合刚夺;如果對(duì)象這時(shí)候還沒(méi)有逃脫献丑,那基本上就是真的被回收了末捣。
這里需要注意的一點(diǎn)就是:一個(gè)對(duì)象如果重寫了 finalize 方法,那么這個(gè)方法最多只會(huì)被執(zhí)行一次创橄。
建議:如非必要箩做,不要重寫該方法。可以使用 try-finally 代替妥畏,此方式更好邦邦,更及時(shí)。同時(shí)注意:在 Mysql 的 JDBC 驅(qū)動(dòng)中醉蚁,com.mysql.jdbc.ConnectionImpl 就實(shí)現(xiàn)了 finalize 方法燃辖,作用是:當(dāng)一個(gè) JDBC Connection 被回收時(shí),需要進(jìn)行連接的關(guān)閉网棍,如果開(kāi)發(fā)人員忘記了關(guān)閉黔龟,則在 finalize 方法中進(jìn)行關(guān)閉。但是滥玷,由于其調(diào)用的不確定性氏身,這不能單獨(dú)作為可靠的資源回收手段。
到這里惑畴,我們知道了什么時(shí)候進(jìn)行回收:如果一個(gè)對(duì)象重寫了 finalize 方法且這個(gè)方法沒(méi)有被 JVM 調(diào)用過(guò)蛋欣,那么這個(gè)對(duì)象會(huì)被放入一個(gè)隊(duì)列等待被一個(gè)低優(yōu)先級(jí)的線程執(zhí)行 finalize 方法,如果在這個(gè)方法中對(duì)象不能自救如贷,則這個(gè)對(duì)象在第二次標(biāo)記過(guò)程中就會(huì)被標(biāo)記死亡陷虎,等待 GC 回收。
3. 如何回收杠袱?
如何回收尚猿,這個(gè)問(wèn)題非常的大,涉及到各種垃圾回收算法霞掺,各種垃圾收集器谊路。限于本篇的篇幅,樓主將不會(huì)在這篇文章里深入探討菩彬,這里只會(huì)列出一些大綱缠劝,這些大綱將是后面文章的摘要潮梯,我們將在后面的文章中深入探討如何回收。
那么惨恭,有哪些摘要呢秉馏?
3.1 垃圾回收算法
- 標(biāo)記清除算法
- 復(fù)制算法
- 標(biāo)記整理算法
- 分代收集算法(堆如何分代)
這些算法是 GC 的基礎(chǔ),所有 GC 的實(shí)現(xiàn)都是基于這些算法來(lái)清除無(wú)用對(duì)象脱羡,然后釋放內(nèi)存空間萝究。我們將會(huì)在后面的文章一個(gè)一個(gè)講解。
3.2 有哪些垃圾收集器
- Serial 串行收集器(只適用于堆內(nèi)存256m 一下的 JVM )
- ParNew 并行收集器(Serial 收集器的多線程版本)
- Parallel Scavenge (PS 收集器锉罐,該收集器以吞吐量為主要目的帆竹,是1.8的默認(rèn) GC)
- CMS 收集器(該收集器全稱 Concurrent Mark Sweep,是一種關(guān)注最短停頓時(shí)間的垃圾收集器)
- G1 收集器(JDK 9 的默認(rèn) GC)
3.3 有哪些GC
- Young GC(又稱 YGC脓规,minor GC栽连,年輕代 GC)
- Old GC (老年代 GC,只有 CMS 才會(huì)單獨(dú)回收 Old 區(qū))
- Full GC(又稱 major GC)
- Mixed GC(混合 GC侨舆,G1 收集器獨(dú)有)
好秒紧,以上就是如何回收的大綱,我們將在后面的文章中慢慢講解挨下。
總結(jié)
這篇文章主要總結(jié)了什么是 GC 熔恢,以及 GC 的作用,GC 主要做了3件事情臭笆,哪些內(nèi)存需要回收叙淌,什么時(shí)候回收,如何回收耗啦。我們知道了 GC 通過(guò)可達(dá)性分析知道了哪些內(nèi)存需要回收凿菩,那什么時(shí)候回收呢?執(zhí)行 finalize 方法后如果還沒(méi)有復(fù)活帜讲,將被回收衅谷。第三個(gè)問(wèn)題:如何回收呢?這個(gè)問(wèn)題是一個(gè)大課題似将,我們只是列出了一些大綱获黔,比如有哪些垃圾收集器,有哪些垃圾算法在验,有哪些 GC 過(guò)程玷氏。這些細(xì)節(jié)我們將在后面慢慢講解,逐步深入腋舌。