概述
說到垃圾收集(Garbage Collection警没,GC),大部分人都會(huì)把這項(xiàng)技術(shù)當(dāng)做 Java 語言的伴生產(chǎn)物缔刹。事實(shí)上球涛,GC的歷史比Java久遠(yuǎn),1960年誕生于MIT的Lisp是第一門真正使用內(nèi)存動(dòng)態(tài)分配和垃圾收集技術(shù)的語言校镐。當(dāng)Lisp還在胚胎時(shí)期時(shí)亿扁,人們就在思考GC需要完成的3件事情:
- 哪些內(nèi)存需要回收?
- 什么時(shí)候回收鸟廓?
- 如何回收从祝?
經(jīng)過半個(gè)多時(shí)間的發(fā)展,目前內(nèi)存的動(dòng)態(tài)分配與內(nèi)存回收技術(shù)已經(jīng)相當(dāng)?shù)某墒煲眨磺锌雌饋矶歼M(jìn)入了“自動(dòng)化”時(shí)代牍陌,那為什么我們還要去了解GC和內(nèi)存分配呢?答案很簡單:當(dāng)需要排查各種內(nèi)存溢出员咽、內(nèi)存泄漏問題時(shí)毒涧,當(dāng)垃圾收集成為系統(tǒng)達(dá)到更高并發(fā)量的瓶頸時(shí),我們就需要對(duì)這些“自動(dòng)化”的技術(shù)實(shí)施必要的監(jiān)控和調(diào)節(jié)骏融。
在 深入理解JVM虛擬機(jī) - JVM運(yùn)行時(shí)數(shù)據(jù)區(qū) 中链嘀,介紹了JVM內(nèi)存運(yùn)行時(shí)區(qū)域的各個(gè)部分萌狂,其中 程序計(jì)數(shù)器、虛擬機(jī)棧怀泊、本地方法棧3個(gè)區(qū)域隨線程而生茫藏,隨線程而滅,因此在這幾個(gè)區(qū)域內(nèi)就不需要過多考慮回收的問題霹琼,因?yàn)榉椒ńY(jié)束或者線程結(jié)束時(shí)务傲,內(nèi)存自然就跟隨者回收了。而Java 堆和方法區(qū)則不一樣枣申,一個(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)存,本文后續(xù)討論中的“內(nèi)存”分配與回收也僅指這一部分內(nèi)存模孩。
如何判定對(duì)象已死亡
對(duì)象“存活”判定算法
在堆里面存放著Java世界中幾乎所有的對(duì)象實(shí)例尖阔,垃圾收集器在對(duì)堆進(jìn)行回收前,第一件事情就是要確定這些對(duì)象之中哪些還“存活”著榨咐,哪些已經(jīng)“死亡”(即不可能被任何途徑使用的對(duì)象)介却。
1、引用計(jì)數(shù)算法
原理:給對(duì)象中添加一個(gè)引用計(jì)數(shù)器块茁,每當(dāng)有一個(gè)地方引用它時(shí)齿坷,計(jì)數(shù)器加1;引用失效時(shí)数焊,計(jì)數(shù)器減1永淌;任何時(shí)刻計(jì)數(shù)器為0的對(duì)象就是不可能再被使用的。 缺點(diǎn):很難解決對(duì)象相互循環(huán)引用的問題(兩個(gè)對(duì)象相互循環(huán)引用昌跌,但其實(shí)他們都已經(jīng)沒有用了)仰禀。
2照雁、可達(dá)性分析算法
在主流的商用程序語言(Java蚕愤、C#、Lisp)的主流實(shí)現(xiàn)中都是通過可達(dá)性分析(Reachability Analysis)來判定對(duì)象是否存活的饺蚊。
原理:通過一些列稱為“GC Roots”的對(duì)象作為起始點(diǎn)萍诱,從這些節(jié)點(diǎn)開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain)污呼,當(dāng)一個(gè)對(duì)象到GC Roots沒有任何引用鏈相連(用圖論的話來說裕坊,就是從GC Roots到這個(gè)對(duì)象不可達(dá))時(shí),則證明此對(duì)象是不可用的燕酷。
在Java語言中籍凝,可作為GC Roots的對(duì)象包括下面幾種:
- 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象周瞎。
- 方法區(qū)中類靜態(tài)屬性引用的對(duì)象。
- 方法區(qū)中常量引用的對(duì)象饵蒂。
- 本地方法棧中JNI(即一般說的Native方法)引用的對(duì)象声诸。
再談引用
無論是通過引用計(jì)數(shù)算法判斷對(duì)象的引用數(shù)量,還是通過可達(dá)性分析算法判斷對(duì)象的引用鏈?zhǔn)欠窨蛇_(dá)退盯,判定對(duì)象是否存活都與“引用”有關(guān)彼乌。
在JDK1.2之后,Java對(duì)引用的概念進(jìn)行了擴(kuò)充渊迁,將引用分為強(qiáng)引用(Strong Reference)慰照、軟引用(Soft Reference)、弱引用(Weak Reference)琉朽、虛引用(Phantom Reference)4種毒租,這4種應(yīng)用強(qiáng)度依次逐漸減弱。
強(qiáng)引用
強(qiáng)引用就是指在程序代碼之中普遍存在的箱叁,類似“Object object = new Object()
”這類的引用蝌衔,只要強(qiáng)引用還存在,垃圾收集器永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象蝌蹂。
軟引用
軟引用是用來描述一些還在用但并非必需的對(duì)象噩斟。對(duì)于軟引用關(guān)聯(lián)著的對(duì)象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前孤个,將會(huì)把這些對(duì)象列進(jìn)回收范圍之中進(jìn)行第二次回收剃允,如果這次回收完成還沒有足夠的內(nèi)存,才會(huì)拋出內(nèi)存溢出異常齐鲤。在JDK1.2之后斥废,提供了SoftReference類來實(shí)現(xiàn)軟引用。
弱引用
弱引用也是用來描述非必需對(duì)象的给郊,但是它的強(qiáng)度比軟引用要更弱一些牡肉,被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次垃圾收集發(fā)生之前。當(dāng)垃圾收集器工作時(shí)淆九,無論當(dāng)前內(nèi)存是否足夠统锤,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象。在JDK1.2之后炭庙,提供了WeakReference類來實(shí)現(xiàn)弱引用饲窿。
虛引用
虛引用也稱為幽靈引用或者幻影引用,它是最弱的一種引用關(guān)系焕蹄。一個(gè)對(duì)象是否有虛引用的存在逾雄,完全不會(huì)對(duì)其生存時(shí)間構(gòu)成影響,也無法通過虛引用來取得一個(gè)對(duì)象實(shí)例。為一個(gè)對(duì)象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個(gè)對(duì)象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知鸦泳。在JDK1.2之后银锻,提供了PhantomReference類來實(shí)現(xiàn)虛引用。
回收方法區(qū)
很多人認(rèn)為方法區(qū)(或者Hotspot虛擬機(jī)中的永久代)是沒有垃圾收集的做鹰,Java虛擬機(jī)規(guī)范中確實(shí)說過可以不要求虛擬機(jī)在方法區(qū)實(shí)現(xiàn)垃圾收集徒仓,而且在方法區(qū)中進(jìn)行垃圾收集的"性價(jià)比" 一般比較低:在堆中,尤其是新生代中誊垢,常規(guī)應(yīng)用進(jìn)行一次垃圾收集一般可以回收70% ~ 95%的空間掉弛,而永久代的垃圾收集效率遠(yuǎn)低于此。
永久代的垃圾收集主要回收兩部分內(nèi)容:廢棄常量和無用的類喂走。
垃圾收集算法
1殃饿、標(biāo)記-清除算法(Mark-Sweep)
原理:
標(biāo)記-清除算法(Mark-Sweep)分為“標(biāo)記”和“清除”兩個(gè)階段:首先標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象芋肠。
缺點(diǎn):
1.效率問題乎芳,標(biāo)記和清除兩個(gè)過程的效率都不高;
2.空間問題帖池,標(biāo)記清除后會(huì)產(chǎn)生大量的不連續(xù)的內(nèi)存碎片奈惑,空間碎片太多可能會(huì)導(dǎo)致以后再程序運(yùn)行過程中需要分配較大對(duì)象時(shí),無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動(dòng)作睡汹。
2肴甸、復(fù)制算法
原理:
它將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊囚巴。當(dāng)這一塊的內(nèi)存用完了原在,就將還存活著的對(duì)象復(fù)制到另外一塊上面,然后再把已經(jīng)使用過的內(nèi)存一次清理掉彤叉。這樣使得每次都是對(duì)整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收庶柿,內(nèi)存分配時(shí)也就不用考慮內(nèi)存碎片等復(fù)雜情況,只要移動(dòng)堆頂指針秽浇,按順序分配內(nèi)存即可浮庐,實(shí)現(xiàn)簡單,運(yùn)行高效柬焕。
缺點(diǎn):
需要復(fù)制审残,效率降低、浪費(fèi)空間击喂。
現(xiàn)在的商業(yè)虛擬機(jī)都采用這種收集算法來回收新生代碉熄。IBM公司的專門研究表明贫堰,新生代中對(duì)象98%都是“朝生夕死”的君珠,所以并不需要按照1:1的比例來劃分內(nèi)存空間薪缆,而是將內(nèi)存分為一塊較大的Eden空間和兩塊較小的Survivor空沼填,每次使用Eden和其中一塊Survivor。當(dāng)回收時(shí)挖藏,將Eden和Survivor中還存活著的對(duì)象一次性地復(fù)制到另外一塊Survivor空間上最楷,最后清理掉Eden和剛才用過的Survivor空間。
3铲敛、標(biāo)記-整理算法(Mark-Compact)
原理: 標(biāo)記過程任然與“標(biāo)記-清除”算法一樣褐澎,但后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行清理,而是讓所有存活對(duì)象都向一端移動(dòng)伐蒋,然后直接清理掉端邊界以外的內(nèi)存工三。
分代收集算法(Generational Collection)
分代的垃圾回收策略,是基于這樣一個(gè)事實(shí):不同的對(duì)象的生命周期是不一樣的先鱼。因此俭正,不同生命周期的對(duì)象可以采取不同的收集方式,以便提高回收效率焙畔。
4掸读、分代收集算法
當(dāng)前的商業(yè)虛擬機(jī)的垃圾收集都采用“分代收集”(Generational Collection) 算法,這種算法并沒有什么新的思想宏多,只是根據(jù)對(duì)象存活周期的不同將內(nèi)存劃分為幾塊儿惫。
一般是把 Java 堆分為 年輕代 和老年代,這樣就可以根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴ā?br>
在年輕代中伸但,每次垃圾收集時(shí)都發(fā)現(xiàn)有大批的對(duì)象死去肾请,只有少量存活,那就選用復(fù)制算法更胖,只需要付出少量存活對(duì)象的復(fù)制成本就可以完成收集筐喳。
而老年代中因?yàn)閷?duì)象存活率高、沒有額外空間對(duì)它進(jìn)行分配擔(dān)保函喉,就必須使用“標(biāo)記-清理”或者 **“標(biāo)記-整理” **算法來進(jìn)行回收避归。
垃圾收集器
如果說收集算法是內(nèi)存回收的方法論,那么垃圾收集器就是內(nèi)存回收的具體實(shí)現(xiàn)管呵。Java虛擬機(jī)規(guī)范對(duì)垃圾收集器應(yīng)該如何實(shí)現(xiàn)并沒有任何規(guī)定梳毙,因此不同的廠商,不同版本的虛擬機(jī)所提供的垃圾收集器都可能會(huì)有很大差別捐下,并且一般都會(huì)提供參數(shù)供用戶根據(jù)自己的應(yīng)用特點(diǎn)和要求組合出各個(gè)年代所使用的收集器账锹。這里討論的收集器基于JDK1.7 Update 14之后的HotSpot虛擬機(jī)(在這個(gè)版本中正式提供了商用的G1收集器,之前G1仍處于試驗(yàn)狀態(tài))坷襟,這個(gè)虛擬機(jī)包含的所有收集器如下圖所示:
上圖展示了7種作用于不同分代的收集器奸柬,如果兩個(gè)收集器之間存在連線,就說明它們可以搭配使用婴程。收集器所處的區(qū)域廓奕,則表示它是屬于新生代收集器還是老年代收集器。
在介紹這些收集器各種的特性之前,我們先來明確一個(gè)觀點(diǎn):雖然我們是在對(duì)各個(gè)收集器進(jìn)行比較桌粉,但并非為了挑選出一個(gè)最好的收集器蒸绩。因?yàn)橹钡侥壳盀橹惯€沒有最好的收集器出現(xiàn),更加沒有萬能的收集器铃肯,所以我們選擇的只是對(duì)具體應(yīng)用最合適的收集器患亿。
1、Serial收集器
它是一個(gè)單線程的收集器押逼,但它的“單線程”的意義并不僅說明它只會(huì)使用一個(gè)CPU或者一條收集線程去完成垃圾收集工作步藕,更重要的是它在進(jìn)行垃圾收集時(shí),必須暫停其他所有的工作線程挑格,直到它收集結(jié)束漱抓。
2、ParNew收集器
ParNew收集器其實(shí)就是Serial收集器的多線程版本恕齐,除了使用多條線程進(jìn)行垃圾收集之外乞娄,其余行為包括Serial收集器可用的所有控制參數(shù)、收集算法显歧、Stop The World仪或、對(duì)象分配規(guī)則、回收策略等都與Serial收集器完全一樣士骤。
3范删、Parallel Scavenge收集器
Parallel Scavenge收集器是一個(gè)新時(shí)代收集器,它也是使用復(fù)制算法的收集器拷肌,又是并行的多線程收集器到旦。
Parallel Scavenge收集器的特點(diǎn)是它的關(guān)注點(diǎn)與其他收集器不同,CMS等收集器的關(guān)注點(diǎn)是盡可能的縮短垃圾收集時(shí)用戶線程暫停的時(shí)間巨缘,而Parallel Scavenge收集器的目標(biāo)則是達(dá)到一個(gè)可控制的吞吐量(Throughout)添忘。所謂吞吐量就是CPU運(yùn)行用戶代碼的時(shí)間和總耗時(shí)的比值,即吞吐量=運(yùn)行用戶代碼時(shí)間/(運(yùn)行用戶代碼時(shí)間+垃圾收集時(shí)間)若锁。
4搁骑、Serial Old收集器
Serial Old是Serial收集器的老年代版本,它同樣是一個(gè)單線程收集器又固,使用“標(biāo)記-整理”算法仲器。
5、Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本仰冠。
6乏冀、CMS收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。
7洋只、G1收集器
G1收集器