1.如何判定對象已死
判斷對象是否已死有兩種方法坦弟,一種是引用計(jì)數(shù)法,另一種是可達(dá)性分析算法钱贯。
1.1引用計(jì)數(shù)法
給對象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí)措伐,計(jì)數(shù)器值就加1特纤;當(dāng)引用失效時(shí),計(jì)數(shù)器就減一侥加;任何時(shí)刻為0的對象就是不肯再被使用的捧存。
1.2.可達(dá)性分析
通過一系列的稱為“GC Roots”的對象作為起始點(diǎn),從這些節(jié)點(diǎn)開始向下搜索担败,搜索所走過的路稱為引用鏈昔穴,當(dāng)一個(gè)對象到GC Roots沒有人格引用鏈項(xiàng)鏈,則證明對象是不可用的提前。在java語言中吗货,可以作為GC Roots的對象包括下面幾種:
1.2.1.虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象。
1.2.2.方法區(qū)中靜態(tài)屬性引用的對象狈网。
1.2.3.方法區(qū)中常量引用的對象宙搬。
1.2.4.本地方法棧中JNI(即一般說的Native方法)引用的對象。
2.java中的四種引用
2.1. 強(qiáng)引用:類似“Object o = new Object()”拓哺,只要強(qiáng)引用還存在勇垛,就不會(huì)被回收;
2.2.軟引用:用來描述一些有用但非必須的對象士鸥。如果一個(gè)對象只具有軟引用闲孤,則內(nèi)存空間足夠, 垃圾回收器就不會(huì)回收它烤礁;如果內(nèi)存空間不足了讼积,就會(huì)回收這些對象的內(nèi)存。只要垃圾回收器沒 有回收它鸽凶,該對象就可以被程序使用币砂。軟引用可用來實(shí)現(xiàn)內(nèi)存敏感的高速緩存(如果內(nèi)存夠建峭,軟 引用沒有被回收玻侥,則可以直接使用,如果內(nèi)存不夠亿蒸,軟引用已經(jīng)被回收凑兰,則重新讀取數(shù)據(jù)(如從 數(shù)據(jù)庫中))。(java.lang.ref 包)SoftReferencesoftRef = new SoftReference(str);
2.3.弱引用:也是用來描述非必須對象的边锁,但是它的強(qiáng)度比軟引用更弱一些姑食,被弱引用關(guān)聯(lián)的對 象只能生存到下一次垃圾收集發(fā)生之前。當(dāng)垃圾收集器工作時(shí)茅坛,無論當(dāng)前內(nèi)存是否足夠音半,都會(huì)回 收只被弱引用關(guān)聯(lián)的對象则拷。如果這個(gè)對象是偶爾的使用,并且希望在使用時(shí)隨時(shí)就能獲取到曹鸠,但 又不想影響此對象的垃圾收集煌茬,那么你應(yīng)該用 Weak Reference 來記住此對象。
2.4.虛引用:它是最弱的一種引用關(guān)系彻桃,一個(gè)對象是否有虛引用的存在坛善,完全不會(huì)對其生存時(shí)間 構(gòu)成影響,也無法通過虛引用來取得一個(gè)對象實(shí)例邻眷。為一個(gè)對象設(shè)置虛引用的唯一目的就是能在 這個(gè)對象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知眠屎。jdk1.2以后,提供了PhantomReference類來實(shí)現(xiàn)虛引用肆饶。
3.回收無效對象的過程
finalize()方法
即使在可達(dá)性分析算法中不可達(dá)的對象改衩,也并非是“非死不可”的,這時(shí)候它們暫時(shí)處于 “緩刑”階段抖拴,要真正宣告一個(gè)對象死亡,至少要經(jīng)歷兩次標(biāo)記過程阿宅。標(biāo)記的前提是對象在進(jìn) 行可達(dá)性分析后發(fā)現(xiàn)沒有與 GC Roots 相連接的引用鏈候衍。
3.1.第一次標(biāo)記并進(jìn)行一次篩選。
篩選的條件是此對象是否有必要執(zhí)行 finalize()方法洒放。當(dāng)對象沒有覆蓋 finalize 方法蛉鹿,或者 finalize 方法已經(jīng)被虛擬機(jī)調(diào)用過(finalize 只會(huì)調(diào)用一次),虛擬機(jī)將這兩種情況都視為“沒 有必要執(zhí)行”往湿,對象被回收妖异。
3.2.第二次標(biāo)記
如果這個(gè)對象被判定為有必要執(zhí)行 finalize()方法,那么這個(gè)對象將會(huì)被放置在一個(gè) 名為:F-Queue 的隊(duì)列之中领追,并在稍后由一條虛擬機(jī)自動(dòng)建立的他膳、低優(yōu)先級的 Finalizer 線 程去執(zhí)行。這里所謂的“執(zhí)行”是指虛擬機(jī)會(huì)觸發(fā)這個(gè)方法绒窑,但并不承諾會(huì)等待它運(yùn)行結(jié)束棕孙。 這樣做的原因是,如果一個(gè)對象 finalize()方法中執(zhí)行緩慢些膨,或者發(fā)生死循環(huán)(更極端的 情況)蟀俊,將很可能會(huì)導(dǎo)致 F-Queue 隊(duì)列中的其他對象永久處于等待狀態(tài),甚至導(dǎo)致整個(gè)內(nèi)存 回收系統(tǒng)崩潰订雾。
finalize()方法是對象脫逃死亡命運(yùn)的最后一次機(jī)會(huì)肢预,稍后 GC 將對 F-Queue 中的對 象進(jìn)行第二次小規(guī)模標(biāo)記,如果對象要在 finalize()中成功拯救自己----只要重新與引用 鏈上的任何的一個(gè)對象建立關(guān)聯(lián)即可洼哎,譬如把自己賦值給某個(gè)類變量或?qū)ο蟮某蓡T變量烫映,那 在第二次標(biāo)記時(shí)它將移除出“即將回收”的集合沼本。如果對象這時(shí)候還沒逃脫,那基本上它就真 的被回收了锭沟。
4.回收方法區(qū)
方法區(qū)中主要清除兩種垃圾:
4.1. 廢棄常量
4.2. 無用的類
4.1.1.判斷廢棄的常量
清除廢棄的常量和清除對象類似擅威,只要常量池中的常量不被任何變量或?qū)ο笠茫敲催@些常量就會(huì)被清除掉冈钦。
4.2.1.如何判斷廢棄的類
清除廢棄類的條件較為苛刻:
4.2.1. 該類的所有對象都已被清除郊丛。
4.2.2. 該類的java.lang.Class對象沒有被任何對象或變量引用。
只要一個(gè)類被虛擬機(jī)加載進(jìn)方法區(qū)瞧筛,那么在堆中就會(huì)有一個(gè)代表該類的對象:java.lang.Class厉熟。這個(gè)對象在類被加載進(jìn)方法區(qū)的時(shí)候創(chuàng)建,在方法區(qū)中該類被刪除時(shí)清除较幌。
4.2.3. 加載該類的ClassLoader已經(jīng)被回收揍瑟。
5.垃圾收集算法
5.1.標(biāo)記-清除算法(Mark-Sweep)。
首先標(biāo)記處所有需要回收的對象乍炉,在標(biāo)記完成后統(tǒng)一回 收绢片。缺點(diǎn):標(biāo)記和清除兩個(gè)過程都效率低;標(biāo)記清除后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片岛琼,空間 碎片太多可能會(huì)導(dǎo)致以后在程序運(yùn)行中需要分配大對象時(shí)底循,無法找到足夠的連續(xù)內(nèi)存而不得 不提取觸發(fā) GC。
5.2.復(fù)制算法槐瑞。
將可用內(nèi)存按容量劃分成大小相等的兩塊熙涤,每次只使用一塊。當(dāng)這一塊使用 完了困檩,就將還存活著的對象復(fù)制到另一塊上面祠挫,然后再把已使用過的內(nèi)存一次清理掉。這樣 不用考慮內(nèi)存碎片的問題悼沿,只要移動(dòng)堆頂指針等舔,按順序分配即可,實(shí)現(xiàn)簡單糟趾、運(yùn)行高效慌植。缺點(diǎn):內(nèi)存縮小為原來的一半。現(xiàn)代商用虛擬機(jī)都采用這種算法回收新生代拉讯。而新生代中約 98%的對象都是“朝生夕死”涤浇,所以不需按 1:1 劃分鳖藕。HotSpot 默認(rèn) Eden 和 Survivor 是 8:1魔慷,所以每次可用內(nèi)存為 90%。但 我們沒法保證每次回收只有不多于 10%的對象存活著恩,當(dāng) Survivor 空間不夠時(shí)院尔,需要依賴其他 內(nèi)存(這里指老年代)進(jìn)行分配擔(dān)保(直接進(jìn)入老年代)蜻展。
缺點(diǎn):如果對象存活率太高,要進(jìn)行較多復(fù)制操作邀摆,效率低纵顾。且需要額外空間擔(dān)保,老 年代不能選用這種算法栋盹。
3施逾、標(biāo)記-整理算法。
過程與“標(biāo)記-清除”一樣例获,但后續(xù)步驟不是直接對可回收對象進(jìn)行清理汉额,而是讓所有存 活的對象都向一端移動(dòng),然后直接清理掉端邊界以外的內(nèi)存榨汤。老年代因?yàn)閷ο蟠婊盥矢呷渌选] 有額外空間進(jìn)行分配擔(dān)保,必須使用“標(biāo)記-清理”或“標(biāo)記-整理”算法收壕。
4. 分代收集算法
將內(nèi)存劃分為老年代和新生代妓灌。老年代中存放壽命較長的對象,新生代中存放“朝生夕死”的對象蜜宪。然后在不同的區(qū)域使用不同的垃圾收集算法虫埂。
6.JVM垃圾收集器
6.1.Serial 是一個(gè)單線程收集器,在它進(jìn)行垃圾收集時(shí)圃验,必須暫停其他所有工作線程(StopTheWorld)告丢;簡單高效,是虛擬機(jī)在 Client模式下默認(rèn)的新生代收集器(復(fù)制算法)损谦。停頓 時(shí)間在幾十到一百多毫秒以內(nèi)岖免,可以接受。
6.2.ParNew 其實(shí)就是 Serial 收集器的多線程版本照捡;ParNew 收集器是許多運(yùn)行在 Server模式下的虛擬機(jī)中首選的新生代收集器颅湘。除去性能因素,很重要的原因是除了 Serial 收集 器外栗精,目前只有它能與 CMS收集器(老年代)配合工作闯参。(復(fù)制算法)
但是,在單 CPU環(huán)境中悲立,ParNew收集器絕對不會(huì)有比 Serial 收集器更好的效果鹿寨,甚至 由于存在線程交互的開銷,該收集器在通過超線程技術(shù)實(shí)現(xiàn)的兩個(gè) CPU的環(huán)境中都不能百分 之百地保證可以超越 Serial收集器薪夕。然而脚草,隨著可以使用的 CPU的數(shù)量的增加,它對于 GC 時(shí)系統(tǒng)資源的有效利用還是很有好處的原献。
6.3.Parallel Scavenge 收集器是新生代垃圾收集器馏慨,使用復(fù)制算法埂淮,也是并行的多線程 收集器。與 ParNew 收集器相比写隶,很多相似之處倔撞,但是 Parallel Scavenge 收集器更關(guān)注可控 制的吞吐量(運(yùn)行用戶代碼時(shí)間/(運(yùn)行用戶代碼+垃圾收集時(shí)間))。吞吐量越大慕趴,垃圾收集 的時(shí)間越短痪蝇,則用戶代碼則可以充分利用 CPU 資源,盡快完成程序的運(yùn)算任務(wù)冕房。
直觀上霹俺,只要最大的垃圾收集停頓時(shí)間越小,吞吐量是越高的毒费,但是 GC 停頓時(shí)間的縮 短是以犧牲吞吐量和新生代空間作為代價(jià)的丙唧。比如原來 10 秒收集一次,每次停頓 100 毫秒觅玻,現(xiàn)在變成 5 秒收集一次想际,每次停頓 70 毫秒。停頓時(shí)間下降的同時(shí)溪厘,吞吐量也下降了胡本。
6.4.Serial Old 收集器是 Serial收集器的老年代版本,也是一個(gè)單線程收集器畸悬,采用“標(biāo)記-整理算法”進(jìn)行回收侧甫。其運(yùn)行過程與 Serial收集器一樣。SerialOld收集器的主要意義也是在于給 Client 模式下的虛擬機(jī)使用蹋宦。如果在 Server模式下披粟,那么它主要還有兩大用途:一種用途是在 JDK 1.5 以及之前的版本中與 Parallel Scavenge收集器搭配使用,另一種用途就是作為 CMS 收集器的后備預(yù)案冷冗,在并發(fā)收集發(fā)生 Concurrent Mode Failure時(shí)使用守屉。
6.5.Parallel Old 收集器是 Parallel Scavenge 收集器的老年代版本,使用多線程和“標(biāo)記-整理”算法進(jìn)行垃圾回收蒿辙。其通常與 Parallel Scavenge 收集器配合使用拇泛,“吞吐量優(yōu)先”收集器 是這個(gè)組合的特點(diǎn),在注重吞吐量和 CPU 資源敏感的場合思灌,都可以使用這個(gè)組合俺叭。
6.6.CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器,基于“標(biāo)記-清除”算法泰偿,從總體上來說熄守,CMS收集器的內(nèi)存回收過程是與用戶線程 一起并發(fā)執(zhí)行的(有的過程也是 StopTheWorld)。
CMS分為四個(gè)步驟:初始標(biāo)記(GCRoots能直接關(guān)聯(lián)到的對象,速度快柠横,可達(dá)性分析, Stop The World)课兄,并發(fā)標(biāo)記(可達(dá)性分析)牍氛,重新標(biāo)記(修正并發(fā)標(biāo)記期間因用戶程序繼續(xù) 運(yùn)作而導(dǎo)致的變動(dòng),速度快烟阐,Stop The World)搬俊,并發(fā)清除
CMS 的優(yōu)點(diǎn)很明顯:并發(fā)收集、低停頓蜒茄。由于進(jìn)行垃圾收集的時(shí)間主要耗在并發(fā)標(biāo)記 與并發(fā)清除這兩個(gè)過程唉擂,雖然初始標(biāo)記和重新標(biāo)記仍然需要暫停用戶線程,但是從總體上看檀葛,
這部分占用的時(shí)間相比其他兩個(gè)步驟很小玩祟,所以可以認(rèn)為是低停頓的。
缺點(diǎn):
對 CPU 資源太敏感屿聋,這點(diǎn)可以這么理解空扎,雖然在并發(fā)標(biāo)記階段用戶線程沒有暫停,但 是由于收集器占用了一部分 CPU 資源润讥,導(dǎo)致程序的響應(yīng)速度變慢
CMS 收集器無法處理浮動(dòng)垃圾转锈。所謂的“浮動(dòng)垃圾”,就是在并發(fā)標(biāo)記階段楚殿,由于用戶程 序在運(yùn)行撮慨,那么自然就會(huì)有新的垃圾產(chǎn)生,這部分垃圾被標(biāo)記過后脆粥,CMS 無法在當(dāng)次集中 處理它們(為什么砌溺?原因在于 CMS 是以獲取最短停頓時(shí)間為目標(biāo)的,自然不可能在一次垃 圾處理過程中花費(fèi)太多時(shí)間)变隔,只好在下一次 GC 的時(shí)候處理抚吠。這部分未處理的垃圾就稱為“浮 動(dòng)垃圾”。由于垃圾收集階段用戶線程還需要運(yùn)行弟胀,那就不能等老年代幾乎全滿了再收集楷力, 一般達(dá)到 92%時(shí)就開始收集,而 CMS 運(yùn)行期間預(yù)留的內(nèi)存無法滿足程序需要孵户,就會(huì)出現(xiàn) “Concurrent Mode Failure”萧朝,此時(shí)將啟動(dòng)備用方案 serial old
由于 CMS 收集器是基于“標(biāo)記-清除”算法的(可能是為了時(shí)間短),前面說過這個(gè)算法會(huì)導(dǎo)致大量的空間碎片的產(chǎn)生夏哭,一旦空間碎片過多检柬,大對象就沒辦法給其分配內(nèi)存,那么即 使內(nèi)存還有剩余空間容納這個(gè)大對象,但是卻沒有連續(xù)的足夠大的空間放下這個(gè)對象埋合,所以 虛擬機(jī)就會(huì)觸發(fā)一次 Full GC卒废。
在使用 CMS收集老年代時(shí),新生代只能選用 ParNew或者 Serial 收集器中的一個(gè)(CMS 與其他不配套伐庭,其他的沒有使用傳統(tǒng)的 GC 收集器框架)
6.7.(Garbage-First)收集器用爪,JDK1.7 才開始商用原押。使用 G1 收集器時(shí),Java 堆內(nèi)存 布局與其他收集器有很大差別偎血,它將整個(gè) Java 堆分為多個(gè)大小相等的獨(dú)立區(qū)域(Region)诸衔, 雖然還保留新生代和老年代的概念,但新生代和老年代不再是物理隔離的了颇玷,他們都是 Region(不需要連續(xù))的集合笨农。
特點(diǎn):并行與并發(fā)。分代收集(不需要其他收集器配合)帖渠≮艘啵空間整合(整體來看采用“標(biāo) 記-整理”,局部(兩個(gè) Region 之間)采用復(fù)制)空郊≌锱可預(yù)測的停頓。
G1 跟蹤各個(gè) Region 里面的垃圾堆積價(jià)值大性尽(回收所獲得的空間大小以及回收所需的 時(shí)間)脾还,在后臺維護(hù)一個(gè)優(yōu)先列表,每次優(yōu)先收集價(jià)值最大的 Region(所以叫 Garbage-First)入愧,
從而保證了 G1 在有限時(shí)間內(nèi)可以獲取盡可能高的收集效率鄙漏。
(老年代)過程:初始標(biāo)記(StopTheWorld)、并發(fā)標(biāo)記棺蛛、最終標(biāo)記(StopTheWorld)怔蚌、篩選回收(Stop The World)
G1 的 YoungGC 就是將 E 區(qū)和 S 區(qū)復(fù)制到灰色的空白區(qū)。
G1 中有 Humongous 區(qū)(巨大區(qū))用于存放比標(biāo)準(zhǔn)塊大 50%的對象
7.JVM垃圾回收機(jī)制
在新生代中旁赊,每次垃圾收集時(shí)都發(fā)現(xiàn)有大批對象死去桦踊,只有少量存活,那就選用復(fù)制算 法终畅,只需要付出少量存活對象的復(fù)制成本就可以完成收集(有 eden和 survivor供復(fù)制籍胯,有 老年代最分配擔(dān)保)。而老年代中因?yàn)閷ο蟠婊盥矢呃敫!]有額外空間對它進(jìn)行分配擔(dān)保杖狼,就 必須使用“標(biāo)記-清理”或者“標(biāo)記-整理”算法來進(jìn)行回收。
發(fā)生 Minor GC妖爷,采用復(fù)制算法蝶涩,發(fā)現(xiàn)
7.1.復(fù)制對象無法全部放入 Survivor,只好通過分配擔(dān)保機(jī)制提前轉(zhuǎn)移到老年代中
7.2.大對象(長字符串或長數(shù)組等需要大量連續(xù)空間的對象)直接進(jìn)入老年代(防止大
對象在 eden和 Survivor中經(jīng)常復(fù)制)通過-XX:PretenureSizeThreshold 參數(shù)設(shè)置 (如 3MB),大于這個(gè)參數(shù)的直接進(jìn)入老年代
7.3.長期存活對象進(jìn)入老年代(默認(rèn) 15歲)
Minor GC:新對象先放入 eden區(qū)绿聘,當(dāng) eden滿了會(huì)觸發(fā) Minor GC嗽上。
Full GC(等于 Major GC):
7.3.1、每次進(jìn)行 Minor GC 時(shí)熄攘,JVM 會(huì)計(jì)算 Survivor 區(qū)移至老年區(qū)的對象的平均大小兽愤,如 果這個(gè)值大于老年區(qū)的剩余值大小則進(jìn)行一次 Full GC
7.3.2、老年代空間不足時(shí)觸發(fā) Full GC鲜屏,只有在新生代對象轉(zhuǎn)入或創(chuàng)建為大對象烹看、大數(shù)組 時(shí)才會(huì)出現(xiàn)不足的現(xiàn)象(大對象直接進(jìn)入老年代)国拇,分配擔(dān)保
7.3.3洛史、永久代滿(永久代 JDK8被移除)
優(yōu)化 Full GC 本身不會(huì)先進(jìn)行 Minor GC,我們可以配置酱吝,讓 Full GC 之前先進(jìn)行一次 Minor GC也殖,因?yàn)槔夏甏芏鄬ο蠖紩?huì)引用到新生代的對象,先進(jìn)行一次 Minor GC可以提高老年代 GC 的速度务热。
在 jvm分帶垃圾回收機(jī)制中忆嗜,將應(yīng)用程序可用的堆空間分為年輕代和老年代,又將年輕 代分為 eden區(qū)崎岂、from區(qū)捆毫、to 區(qū),新建對象總是在 eden 區(qū)中被創(chuàng)建冲甘,當(dāng) eden區(qū)空間已滿绩卤,
就觸發(fā)一次 Minor gc,將還被使用的對象復(fù)制到 from 區(qū)江醇,這樣整個(gè) eden 區(qū)都是未被使用 的空間濒憋,可供繼續(xù)創(chuàng)建對象,當(dāng) eden區(qū)再次用完陶夜,再觸發(fā)一次 Minor gc凛驮,將 eden 區(qū)和from 區(qū)還在被使用的對象復(fù)制到 to 區(qū),下一次 Minor gc則是將 eden 區(qū)和 to區(qū)還被使用的對象 復(fù)制到 from區(qū)条辟。因此黔夭,經(jīng)過多次 Minor gc,某些對象會(huì)在 from區(qū)和 to 區(qū)多次復(fù)制羽嫡,如果 超過某個(gè)閾值對象還未被釋放纠修,則將對象復(fù)制到老年代。如果老年代空間也已用完厂僧,那么就 會(huì)觸發(fā) full gc扣草,即所謂的全量回收。
永久代的垃圾回收主要有兩部分:廢棄常量和無用的類。如沒有任何 String對象引用 “abc”辰妙。在大量使用反射鹰祸、動(dòng)態(tài)代理、CGlib等 ByteCode框架密浑,動(dòng)態(tài)生成 JSP 以及 OSGi這 類頻繁自定義 ClassLoader 的場景都需要虛擬機(jī)具備類卸載功能(回收永久代)蛙婴,以保證永 久代不會(huì)溢出。