JVM面試篇(下)
垃圾收集器
簡述 Java 垃圾回收機(jī)制
在 java 中,程序員是不需要顯示的去釋放一個(gè)對象的內(nèi)存的,而是由虛擬機(jī)自行執(zhí)行。在 JVM
中,有一個(gè)垃圾回收線程,它是低優(yōu)先級的,在正常情況下是不會執(zhí)行的,只有在虛擬機(jī)空閑或
者當(dāng)前堆內(nèi)存不足時(shí),才會觸發(fā)執(zhí)行,掃面那些沒有被任何引用的對象,并將它們添加到要回收
的集合中,進(jìn)行回收。
GC 是什么?為什么要 GC
-
GC 是垃圾收集的意思(Gabage Collection),內(nèi)存處理是編程人員容易出現(xiàn)問題的地方,
忘記或者錯(cuò)誤的內(nèi)存
-
回收會導(dǎo)致程序或系統(tǒng)的不穩(wěn)定甚至崩潰 Java 提供的 GC 功能可以自動監(jiān)測對象是否超過
作用域從而達(dá)到自動
-
回收內(nèi)存的目的, Java語言沒有提供釋放已分配內(nèi)存的顯示操作方法。
垃圾回收的優(yōu)點(diǎn)和原理。2 種回收機(jī)制
Java 語言最顯著的特點(diǎn)就是引入了垃圾回收機(jī)制,它使 Java 程序員在編寫程序時(shí)不再考慮內(nèi)存
管理的問題。
由于有這個(gè)垃圾回收機(jī)制, Java 中的對象不再有“作用域”的概念,只有引用的對象才有“作
用域”。
-
垃圾回收機(jī)制有效的防止了內(nèi)存泄露,可以有效的使用可使用的內(nèi)存。
-
垃圾回收器通常作為一個(gè)單獨(dú)的低級別的線程運(yùn)行,在不可預(yù)知的情況下對內(nèi)存堆中已經(jīng)死
亡的或很長時(shí)間沒有用過的對象進(jìn)行清除和回收。
程序員不能實(shí)時(shí)的對某個(gè)對象或所有對象調(diào)用垃圾回收器進(jìn)行垃圾回收。垃圾回收有分代復(fù)制
垃圾回收、標(biāo)記垃圾回收、增量垃圾回收。
垃圾回收器的基本原理是什么?
對于 GC 來說,當(dāng)程序員創(chuàng)建對象時(shí),GC 就開始監(jiān)控這個(gè)對象的地址、大小以及使用情況。
主動通知虛擬機(jī)進(jìn)行垃圾回收的辦法?
通常,GC 采用有向圖的方式記錄和管理堆(heap)中的所有對象。通過這種方式確定哪些對象
是"可達(dá)的",哪些對象是"不可達(dá)的"。當(dāng) GC 確定一些對象為" 不可達(dá)"時(shí),GC 就有責(zé)任回收這
些內(nèi)存間。
垃圾回收器可以馬上回收內(nèi)存嗎?
可以。程序員可以手動執(zhí)行 System.gc(),通知 GC 運(yùn)行,但是 Java 語言規(guī)范并不保證 GC 一
定會執(zhí)行。
我們能保證 GC 執(zhí)行嗎?
不能,雖然你可以調(diào)用 System.gc() 或者 Runtime.gc(),但是沒有辦法保證GC 的執(zhí)行。
Java 中引用類型有哪些?
-
強(qiáng)引用:發(fā)生 gc 的時(shí)候不會被回收。
-
軟引用:有用但不是必須的對象,在發(fā)生內(nèi)存溢出之前會被回收。
-
弱引用:有用但不是必須的對象,在下一次 GC 時(shí)會被回收。
-
虛引用(幽靈引用/幻影引用):無法通過虛引用獲得對象,用PhantomReference 實(shí)現(xiàn)虛
引用,虛引用的用途是在 gc 時(shí)返回一個(gè)通知。
強(qiáng)引用、軟引用、弱引用、虛引用的區(qū)別?
思路: 先說一下四種引用的定義,可以結(jié)合代碼講一下,也可以擴(kuò)展談到ThreadLocalMap
里弱引用用處。
-
強(qiáng)引用
我們平時(shí) new 了一個(gè)對象就是強(qiáng)引用,例如 Object obj = new Object();即使在內(nèi)存不足
的情況下,JVM 寧愿拋出 OutOfMemory 錯(cuò)誤也不會回收這種對象
-
軟引用
如果一個(gè)對象只具有軟引用,則內(nèi)存空間足夠,垃圾回收器就不會回收它;如果內(nèi)存空間不
足了,就會回收這些對象的內(nèi)存。
SoftReference<String> softRef=new SoftReference<String>(str); // 軟引用-
用處:軟引用在實(shí)際中有重要的應(yīng)用,例如瀏覽器的后退按鈕。按后退時(shí),這個(gè)后退時(shí)
顯示的網(wǎng)頁內(nèi)容是重新進(jìn)行請求還是從緩存中取出呢?這就要看具體的實(shí)現(xiàn)策略了。
-
如果一個(gè)網(wǎng)頁在瀏覽結(jié)束時(shí)就進(jìn)行內(nèi)容的回收,則按后退查看前面瀏覽過的頁面
時(shí),需要重新構(gòu)建
-
如果將瀏覽過的網(wǎng)頁存儲到內(nèi)存中會造成內(nèi)存的大量浪費(fèi),甚至?xí)斐蓛?nèi)存溢出.
如下代碼
-
-
Browser prev = new Browser(); // 獲取頁面進(jìn)行瀏覽
SoftReference sr = new SoftReference(prev); // 瀏覽完畢后置為軟引用
if(sr.get()!=null){
rev = (Browser) sr.get(); // 還沒有被回收器回收,直接獲取
}else{
prev = new Browser(); // 由于內(nèi)存吃緊,所以對軟引用的對象回收了
sr = new SoftReference(prev); // 重新構(gòu)建
}
-
弱引用
具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過
程中,一旦發(fā)現(xiàn)了只具有弱引用的對象,不管當(dāng)前內(nèi)存空間足夠與否,都會回收它的內(nèi)存。
String str=new String("abc"); WeakReference<String> abcWeakRef = newWeakReference<String>(str); str=null; 等價(jià)于 str = null; System.gc(); -
虛引用
如果一個(gè)對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時(shí)候都可能被垃圾回收
器回收。虛引用主要用來跟蹤對象被垃圾回收器回收的活動。
怎么判斷對象是否可以被回收?
垃圾收集器在做垃圾回收的時(shí)候,首先需要判定的就是哪些內(nèi)存是需要被回收的,哪些對象是
「存活」的,是不可以被回收的;哪些對象已經(jīng)「死掉」了,需要被回收。
一般有兩種方法來判斷:
-
引用計(jì)數(shù)器法:為每個(gè)對象創(chuàng)建一個(gè)引用計(jì)數(shù),有對象引用時(shí)計(jì)數(shù)器 +1,引用被釋放時(shí)計(jì)
數(shù) -1,當(dāng)計(jì)數(shù)器為 0 時(shí)就可以被回收。它有一個(gè)缺點(diǎn)不能解決循環(huán)引用的問題;
-
可達(dá)性分析算法:從 GC Roots 開始向下搜索,搜索所走過的路徑稱為引用鏈。當(dāng)一個(gè)對象
到 GC Roots 沒有任何引用鏈相連時(shí),則證明此對象是可以被回收的。
在 Java 中,對象什么時(shí)候可以被垃圾回收
當(dāng)對象對當(dāng)前使用這個(gè)對象的應(yīng)用程序變得不可觸及的時(shí)候,這個(gè)對象就可以被回收了。
垃圾回收不會發(fā)生在永久代,如果永久代滿了或者是超過了臨界值,會觸發(fā)完全垃圾回收(Full
GC)。如果你仔細(xì)查看垃圾收集器的輸出信息,就會發(fā)現(xiàn)永久代也是被回收的。這就是為什么
正確的永久代大小對避免 Full GC 是非常重要的原因.
JVM 運(yùn)行時(shí)堆內(nèi)存如何分代?
Java 堆從 GC 的角度還可以細(xì)分為: 新生代(Eden 區(qū)、 From Survivor 區(qū)和 To Survivor 區(qū))和
老年代。
參考圖 1:

參考圖 2:

-
從圖中可以看出: 堆大小 = 新生代 + 老年代。其中,堆的大小可以通過參數(shù) –Xms、-Xmx 來
指定。
-
默認(rèn)的,新生代 ( Young ) 與老年代 ( Old ) 的比例的值為 1:2 ( 該值可以通過參數(shù) –
XX:NewRatio 來指定 ),
即:新生代 ( Young ) = 1/3 的堆空間大小。老年代 ( Old ) = 2/3 的堆空間大小。
其中,新生代 ( Young ) 被細(xì)分為 Eden 和 兩個(gè) Survivor 區(qū)域,這兩個(gè)Survivor 區(qū)域分別被命
名為 from 和 to,以示區(qū)分.
默認(rèn)的,Eden: from : to = 8 :1 : 1 ( 可以通過參數(shù)–XX:SurvivorRatio 來設(shè)定 ),即: Eden
= 8/10 的新生代空間大小,from = to = 1/10 的新生代空間大小
JVM 每次只會使用 Eden 和其中的一塊 Survivor 區(qū)域來為對象服務(wù),所以無論什么時(shí)候,總是
有一塊 Survivor 區(qū)域是空閑著的。
因此,新生代實(shí)際可用的內(nèi)存空間為 9/10 ( 即 90% )的新生代空間。
新生代
是用來存放新生的對象。一般占據(jù)堆的 1/3 空間。由于頻繁創(chuàng)建對象,所以新生代會頻繁觸發(fā)
MinorGC 進(jìn)行垃圾回收。新生代又分為 Eden 區(qū)、ServivorFrom、 ServivorTo 三個(gè)區(qū)。
Eden 區(qū)
Java 新對象的出生地(如果新創(chuàng)建的對象占用內(nèi)存很大,則直接分配到老年代)。當(dāng) Eden 區(qū)內(nèi)
存不夠的時(shí)候就會觸發(fā) MinorGC,對新生代區(qū)進(jìn)行一次垃圾回收。
Servivor from 區(qū)
上一次 GC 的幸存者,作為這一次 GC 的被掃描者。
Servivor to 區(qū)
保留了一次 MinorGC 過程中的幸存者。
MinorGC 的過程(復(fù)制->清空->互換)
MinorGC 采用復(fù)制算法。
-
eden、 servicorFrom 復(fù)制到 ServicorTo,年齡+1首先,把 Eden 和 ServivorFrom 區(qū)域
中存活的對象復(fù)制到 ServicorTo 區(qū)域(如果有對象的年齡以及達(dá)到了老年的標(biāo)準(zhǔn),則賦值
到老年代區(qū)),同時(shí)把這些對象的年齡+1(如果 ServicorTo 不夠位置了就放到老年區(qū));
-
清空 eden、 servicorFrom然后,清空 Eden 和 ServicorFrom 中的對象;
-
ServicorTo 和 ServicorFrom 互換最后, ServicorTo 和 ServicorFrom 互換,原
ServicorTo 成為下一次 GC 時(shí)的 ServicorFrom 區(qū)。
老年代
主要存放應(yīng)用程序中生命周期長的內(nèi)存對象。
老年代的對象比較穩(wěn)定,所以 MajorGC (常常稱之為 FULL GC)不會頻繁執(zhí)行。在進(jìn)行 FULL
GC 前一般都先進(jìn)行了一次 MinorGC,使得有新生代的對象晉身入老年代,導(dǎo)致空間不夠用時(shí)才
觸發(fā)。當(dāng)無法找到足夠大的連續(xù)空間分配給新創(chuàng)建的較大對象時(shí)也會提前觸發(fā)一次 MajorGC 進(jìn)
行垃圾回收騰出空間。
FULL GC 采用標(biāo)記清除算法:首先掃描一次所有老年代,標(biāo)記出存活的對象,然后回收沒有標(biāo)記
的對象。ajorGC 的耗時(shí)比較長,因?yàn)橐獟呙柙倩厥铡ULLGC 會產(chǎn)生內(nèi)存碎片,為了減少內(nèi)存
損耗,我們一般需要進(jìn)行合并或者標(biāo)記出來方便下次直接分配。當(dāng)老年代也滿了裝不下的時(shí)候,
就會拋出 OOM(Outof Memory)異常.
永久代
指內(nèi)存的永久保存區(qū)域,主要存放 Class 和 Meta(元數(shù)據(jù))的信息,Class 在被加載的時(shí)候被放
入永久區(qū)域, 它和和存放實(shí)例的區(qū)域不同,GC 不會在主程序運(yùn)行期對永久區(qū)域進(jìn)行清理。所以這
也導(dǎo)致了永久代的區(qū)域會隨著加載的Class 的增多而脹滿,最終拋出 OOM 異常。
JVM 內(nèi)存為什么要分成新生代,老年代,持久代。新生代中為什么要分為 Eden和 Survivor。
思路: 先講一下 JAVA 堆,新生代的劃分,再談?wù)勊鼈冎g的轉(zhuǎn)化,相互之間一些參數(shù)的配置
(如: –XX:NewRatio,–XX:SurvivorRatio 等),再解釋為什么要這樣劃分,最好加一點(diǎn)自己
的理解。
答:
這樣劃分的目的是為了使 JVM 能夠更好的管理堆內(nèi)存中的對象,包括內(nèi)存的分配以及回收。
-
共享內(nèi)存區(qū)劃分
- 共享內(nèi)存區(qū) = 持久帶 + 堆 持久帶 = 方法區(qū) + 其他 Java 堆 = 老年代 + 新生代 新生代 = Eden + S0 + S1 -
一些參數(shù)的配置
默認(rèn)的,新生代 ( Young ) 與老年代 ( Old ) 的比例的值為 1:2 ,可以通過參數(shù) –XX:NewRatio 配置。 默認(rèn)的,Eden : from : to = 8 : 1 : 1 ( 可以通過參數(shù) –XX:SurvivorRatio 來設(shè)定) Survivor 區(qū)中的對象被復(fù)制次數(shù)為 15(對應(yīng)虛擬機(jī)參數(shù)-XX:+MaxTenuringThreshold) -
為什么要分為 Eden 和 Survivor?為什么要設(shè)置兩個(gè) Survivor 區(qū)?
1、如果沒有 Survivor,Eden 區(qū)每進(jìn)行一次 Minor GC,存活的對象就會被送到老年代。 老年代很快被填滿,觸發(fā) Major GC.老年代的內(nèi)存空間遠(yuǎn)大于新生代,進(jìn)行一次 Full GC 消耗的時(shí)間比 Minor GC 長得多,所以需要分為 Eden和 Survivor。 2、Survivor 的存在意義,就是減少被送到老年代的對象,進(jìn)而減少 Full GC 的 發(fā)生,Survivor 的預(yù)篩選保證,只有經(jīng)歷 16 次 Minor GC 還能在新生代中存 活的對象,才會被送到老年代。 3、設(shè)置兩個(gè) Survivor 區(qū)最大的好處就是解決了碎片化,剛剛新建的對象在 Eden 中,經(jīng)歷一次 Minor GC,Eden 中的存活對象就會被移動到第一塊 survivor space S0,Eden 被清空;等 Eden 區(qū)再滿了,就再觸發(fā)一次 Minor GC,Eden 和 S0 中的存活對象又會被復(fù)制送入第二塊 survivor space S1(這 個(gè)過程非常重要,因?yàn)檫@種復(fù)制算法保證了 S1 中來自 S0 和 Eden 兩部分的 存活對象占用連續(xù)的內(nèi)存空間,避免了碎片化的發(fā)生)
JVM 中一次完整的 GC 流程是怎樣的,對象如何晉升到老年代
思路:先描述一下 Java 堆內(nèi)存劃分,再解釋 Minor GC,Major GC,full GC,描述它們之間轉(zhuǎn)
化流程。
答:
-
Java 堆 = 老年代 + 新生代
-
新生代 = Eden + S0 + S1
-
當(dāng) Eden 區(qū)的空間滿了, Java 虛擬機(jī)會觸發(fā)一次 Minor GC,以收集新生代的垃圾,存活
下來的對象,則會轉(zhuǎn)移到 Survivor 區(qū)。
-
大對象(需要大量連續(xù)內(nèi)存空間的 Java 對象,如那種很長的字符串)直接進(jìn)入老年態(tài);
-
如果對象在 Eden 出生,并經(jīng)過第一次 Minor GC 后仍然存活,并且被Survivor 容納的
話,年齡設(shè)為 1,每熬過一次 Minor GC,年齡+1,若年齡超過一定限制(15),則被晉
升到老年態(tài)。即長期存活的對象進(jìn)入老年態(tài)。
-
老年代滿了而無法容納更多的對象,Minor GC 之后通常就會進(jìn)行 Full GC,F(xiàn)ull GC 清理
整個(gè)內(nèi)存堆 – 包括年輕代和年老代。
-
Major GC 發(fā)生在老年代的 GC,清理老年區(qū),經(jīng)常會伴隨至少一次 MinorGC,比 Minor
GC 慢 10 倍以上。
JVM 中的永久代中會發(fā)生垃圾回收嗎
垃圾回收不會發(fā)生在永久代,如果永久代滿了或者是超過了臨界值,會觸發(fā)完全垃圾回收(Full
GC)。如果你仔細(xì)查看垃圾收集器的輸出信息,就會發(fā)現(xiàn)永久代也是被回收的。這就是為什么正
確的永久代大小對避免 Full GC 是非常重要的原因。請參考下 Java8:從永久代到元數(shù)據(jù)區(qū)。
(譯者注:Java8 中已經(jīng)移除了永久代,新加了一個(gè)叫做元數(shù)據(jù)區(qū)的 native 內(nèi)存區(qū))
JAVA8 與元數(shù)據(jù)
在 Java8 中, 永久代已經(jīng)被移除,被一個(gè)稱為“元數(shù)據(jù)區(qū)”(元空間)的區(qū)域所取代。元空間
的本質(zhì)和永久代類似,元空間與永久代之間最大的區(qū)別在于:元空間并不在虛擬機(jī)中,而是使用
本地內(nèi)存。因此,默認(rèn)情況下,元空間的大小僅受本地內(nèi)存限制。 類的元數(shù)據(jù)放入 native
memory, 字符串池和類的靜態(tài)變量放入 java 堆中, 這樣可以加載多少類的元數(shù)據(jù)就不再由
MaxPermSize 控制, 而由系統(tǒng)的實(shí)際可用空間來控制。
如何判斷對象可以被回收?
判斷對象是否存活一般有兩種方式:
-
引用計(jì)數(shù):
每個(gè)對象有一個(gè)引用計(jì)數(shù)屬性,新增一個(gè)引用時(shí)計(jì)數(shù)加 1,引用釋放時(shí)計(jì)數(shù)減1,計(jì)數(shù)為 0
時(shí)可以回收。此方法簡單,無法解決對象相互循環(huán)引用的問題。
-
可達(dá)性分析(Reachability Analysis):
從 GC Roots 開始向下搜索,搜索所走過的路徑稱為引用鏈。當(dāng)一個(gè)對象到GC Roots 沒有
任何引用鏈相連時(shí),則證明此對象是不可用的,不可達(dá)對象。
引用計(jì)數(shù)法
在 Java 中,引用和對象是有關(guān)聯(lián)的。如果要操作對象則必須用引用進(jìn)行。因此,很顯然一個(gè)簡
單的辦法是通過引用計(jì)數(shù)來判斷一個(gè)對象是否可以回收。簡單說,即一個(gè)對象如果沒有任何與之
關(guān)聯(lián)的引用, 即他們的引用計(jì)數(shù)都不為0, 則說明對象不太可能再被用到,那么這個(gè)對象就是可
回收對象。
可達(dá)性分析
為了解決引用計(jì)數(shù)法的循環(huán)引用問題, Java 使用了可達(dá)性分析的方法。通過一系列的“GC
roots”對象作為起點(diǎn)搜索。如果在“GC roots”和一個(gè)對象之間沒有可達(dá)路徑,則稱該對象是
不可達(dá)的。要注意的是,不可達(dá)對象不等價(jià)于可回收對象, 不可達(dá)對象變?yōu)榭苫厥諏ο笾辽僖?jīng)
過兩次標(biāo)記過程。兩次標(biāo)記后仍然是可回收對象,則將面臨回收。
Minor GC 與 Full GC 分別在什么時(shí)候發(fā)生?
新生代內(nèi)存不夠用時(shí)候發(fā)生 MGC 也叫 YGC,JVM 內(nèi)存不夠的時(shí)候發(fā)生 FGC
垃圾收集算法有哪些類型?
-
GC 最基礎(chǔ)的算法有三類: 標(biāo)記 -清除算法、復(fù)制算法、標(biāo)記-壓縮算法,我們常用的垃圾回
收器一般都采用分代收集算法。
-
標(biāo)記 -清除算法,“標(biāo)記-清除”(Mark-Sweep)算法,如它的名字一樣,算法分為“標(biāo)
記”和“清除”兩個(gè)階段:首先標(biāo)記出所有需要回收的對象,在標(biāo)記完成后統(tǒng)一回收掉所有
被標(biāo)記的對象。
-
復(fù)制算法,“復(fù)制”(Copying)的收集算法,它將可用內(nèi)存按容量劃分為大小相等的兩
塊,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對象復(fù)制到另外一塊
上面,然后再把已使用過的內(nèi)存空間一次清理掉。
-
標(biāo)記-壓縮算法,標(biāo)記過程仍然與“標(biāo)記-清除”算法一樣,但后續(xù)步驟不是直接對可回收對
象進(jìn)行清理,而是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存
-
分代收集算法,“分代收集”(Generational Collection)算法,把 Java 堆分為新生代和
老年代,這樣就可以根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴ā?/p>
說一下 JVM 有哪些垃圾回收算法?
-
標(biāo)記-清除算法:標(biāo)記無用對象,然后進(jìn)行清除回收。缺點(diǎn):效率不高,無法清除垃圾碎
片。
-
復(fù)制算法:按照容量劃分二個(gè)大小相等的內(nèi)存區(qū)域,當(dāng)一塊用完的時(shí)候?qū)⒒钪膶ο髲?fù)制到
另一塊上,然后再把已使用的內(nèi)存空間一次清理掉。缺點(diǎn):內(nèi)存使用率不高,只有原來的一
半。
-
標(biāo)記-整理算法:標(biāo)記無用對象,讓所有存活的對象都向一端移動,然后直接清除掉端邊界
以外的內(nèi)存。
-
分代算法:根據(jù)對象存活周期的不同將內(nèi)存劃分為幾塊,一般是新生代和老年代,新生代基
本采用復(fù)制算法,老年代采用標(biāo)記整理算法。
標(biāo)記-清除算法
標(biāo)記無用對象,然后進(jìn)行清除回收。
標(biāo)記-清除算法(Mark-Sweep)是一種常見的基礎(chǔ)垃圾收集算法,它將垃圾收集分為兩個(gè)階
段:
- 標(biāo)記階段:標(biāo)記出可以回收的對象。
- 清除階段:回收被標(biāo)記的對象所占用的空間。
標(biāo)記-清除算法之所以是基礎(chǔ)的,是因?yàn)楹竺嬷v到的垃圾收集算法都是在此算法的基礎(chǔ)上進(jìn)
行改進(jìn)的。
優(yōu)點(diǎn):實(shí)現(xiàn)簡單,不需要對象進(jìn)行移動。
缺點(diǎn):標(biāo)記、清除過程效率低,產(chǎn)生大量不連續(xù)的內(nèi)存碎片,提高了垃圾回收的頻率。
標(biāo)記-清除算法的執(zhí)行的過程如下圖所示:

復(fù)制算法
為了解決標(biāo)記-清除算法的效率不高的問題,產(chǎn)生了復(fù)制算法。它把內(nèi)存空間劃為兩個(gè)相等的區(qū)
域,每次只使用其中一個(gè)區(qū)域。垃圾收集時(shí),遍歷當(dāng)前使用的區(qū)域,把存活對象復(fù)制到另外一個(gè)
區(qū)域中,最后將當(dāng)前使用的區(qū)域的可回收的對象進(jìn)行回收。
優(yōu)點(diǎn):按順序分配內(nèi)存即可,實(shí)現(xiàn)簡單、運(yùn)行高效,不用考慮內(nèi)存碎片。
缺點(diǎn):可用的內(nèi)存大小縮小為原來的一半,對象存活率高時(shí)會頻繁進(jìn)行復(fù)制。
復(fù)制算法的執(zhí)行過程如下圖所示:

標(biāo)記-整理算法
在新生代中可以使用復(fù)制算法,但是在老年代就不能選擇復(fù)制算法了,因?yàn)槔夏甏膶ο蟠婊盥?/p>
會較高,這樣會有較多的復(fù)制操作,導(dǎo)致效率變低。標(biāo)記- 清除算法可以應(yīng)用在老年代中,但是
它效率不高,在內(nèi)存回收后容易產(chǎn)生大量內(nèi)存碎片。因此就出現(xiàn)了一種標(biāo)記-整理算法(Mark-
Compact)算法,與標(biāo)記-整理算法不同的是,在標(biāo)記可回收的對象后將所有存活的對象壓縮到
內(nèi)存的一端,使他們緊湊的排列在一起,然后對端邊界以外的內(nèi)存進(jìn)行回收。回收后,已用和未
用的內(nèi)存都各自一邊。
優(yōu)點(diǎn):解決了標(biāo)記-清理算法存在的內(nèi)存碎片問題。
缺點(diǎn):仍需要進(jìn)行局部對象移動,一定程度上降低了效率。
標(biāo)記-整理算法的執(zhí)行過程如下圖所示:

分代收集算法
分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根據(jù)對象存活的不同生命周期將內(nèi)
存劃分為不同的域,一般情況下將 GC 堆劃分為老生代(Tenured/Old Generation)和新生代
(YoungGeneration)。老生代的特點(diǎn)是每次垃圾回收時(shí)只有少量對象需要被回收,新生代的特點(diǎn)
是每次垃圾回收時(shí)都有大量垃圾需要被回收,因此可以根據(jù)不同區(qū)域選擇不同的算法。
當(dāng)前商業(yè)虛擬機(jī)都采用分代收集的垃圾收集算法。分代收集算法,顧名思義是根據(jù)對象的存活周
期將內(nèi)存劃分為幾塊。一般包括年輕代、老年代 和 永久代,
如圖所示:

當(dāng)前主流 VM 垃圾收集都采用”分代收集” (Generational Collection)算法, 這種算法會根據(jù)對
象存活周期的不同將內(nèi)存劃分為幾塊, 如 JVM 中的 新生代、老年代、永久代, 這樣就可以根據(jù)
各年代特點(diǎn)分別采用最適當(dāng)?shù)?GC 算法。
新生代與復(fù)制算法
每次垃圾收集都能發(fā)現(xiàn)大批對象已死, 只有少量存活. 因此選用復(fù)制算法, 只需要付出少量存活對
象的復(fù)制成本就可以完成收集。
目前大部分 JVM 的 GC 對于新生代都采取 Copying 算法,因?yàn)樾律忻看卫厥斩家厥?/p>
大部分對象,即要復(fù)制的操作比較少,但通常并不是按照 1: 1 來劃分新生代。一般將新生代劃
分為一塊較大的 Eden 空間和兩個(gè)較小的 Survivor 空間(From Space, To Space),每次使用
Eden 空間和其中的一塊 Survivor 空間,當(dāng)進(jìn)行回收時(shí),將該兩塊空間中還存活的對象復(fù)制到另
一塊 Survivor 空間中。

老年代與標(biāo)記復(fù)制算法
因?yàn)槔夏甏鷮ο蟠婊盥矢摺]有額外空間對它進(jìn)行分配擔(dān)保, 就必須采用“標(biāo)記—清理”或“標(biāo)
記—整理” 算法來進(jìn)行回收, 不必進(jìn)行內(nèi)存復(fù)制, 且直接騰出空閑內(nèi)存。因而采用
Mark-Compact 算法。
-
JAVA 虛擬機(jī)提到過的處于方法區(qū)的永生代(Permanet Generation), 它用來存儲 class
類,常量,方法描述等。對永生代的回收主要包括廢棄常量和無用的類
-
對象的內(nèi)存分配主要在新生代的 Eden Space 和 Survivor Space 的From Space(Survivor
目前存放對象的那一塊),少數(shù)情況會直接分配到老生代。
-
當(dāng)新生代的 Eden Space 和 From Space 空間不足時(shí)就會發(fā)生一次 GC,進(jìn)行 GC 后,
EdenSpace 和 From Space 區(qū)的存活對象會被挪到 To Space,然后將 Eden Space 和
FromSpace 進(jìn)行清理。
-
如果 To Space 無法足夠存儲某個(gè)對象,則將這個(gè)對象存儲到老生代。
-
在進(jìn)行 GC 后,使用的便是 Eden Space 和 To Space 了,如此反復(fù)循環(huán)。
-
當(dāng)對象在 Survivor 區(qū)躲過一次 GC 后,其年齡就會+1。 默認(rèn)情況下年齡到達(dá) 15 的對象會
被移到老生代中。
GC 垃圾收集器
Java 堆內(nèi)存被劃分為新生代和年老代兩部分,新生代主要使用復(fù)制和標(biāo)記-清除垃圾回收算法;
年老代主要使用標(biāo)記-整理垃圾回收算法,因此 java 虛擬中針對新生代和年老代分別提供了多種
不同的垃圾收集器, JDK1.6 中 SunHotSpot 虛擬機(jī)的垃圾收集器如下:

說一下 JVM 有哪些垃圾回收器?
如果說垃圾收集算法是內(nèi)存回收的方法論,那么垃圾收集器就是內(nèi)存回收的具體實(shí)現(xiàn)。下圖展示
了 7 種作用于不同分代的收集器,其中用于回收新生代的收集器包括 Serial、PraNew、Parallel
Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,還有用于回收整個(gè) Java
堆的 G1 收集器。不同收集器之間的連線表示它們可以搭配使用。

-
Serial 收集器(復(fù)制算法): 新生代單線程收集器,標(biāo)記和清理都是單線程,優(yōu)點(diǎn)是簡單高效;
-
ParNew 收集器 (復(fù)制算法): 新生代收并行集器,實(shí)際上是 Serial 收集器的多線程版本,在
多核 CPU 環(huán)境下有著比 Serial 更好的表現(xiàn);
-
Parallel Scavenge 收集器 (復(fù)制算法): 新生代并行收集器,追求高吞吐量,高效利用
CPU。吞吐量 = 用戶線程時(shí)間/(用戶線程時(shí)間+GC 線程時(shí)間),高吞吐量可以高效率的利用
CPU 時(shí)間,盡快完成程序的運(yùn)算任務(wù),適合后臺應(yīng)用等對交互相應(yīng)要求不高的場景;
-
Serial Old 收集器 (標(biāo)記-整理算法): 老年代單線程收集器,Serial 收集器的老年代版本;
-
Parallel Old 收集器 (標(biāo)記-整理算法): 老年代并行收集器,吞吐量優(yōu)先,
ParallelScavenge 收集器的老年代版本;
-
CMS(Concurrent Mark Sweep)收集器(標(biāo)記-清除算法): 老年代并行收集器,以獲取最
短回收停頓時(shí)間為目標(biāo)的收集器,具有高并發(fā)、低停頓的特點(diǎn),追求最短GC 回收停頓時(shí)間。
-
G1(Garbage First)收集器 (標(biāo)記-整理算法): Java 堆并行收集器,G1 收集器是JDK1.7 提
供的一個(gè)新收集器,G1 收集器基于“標(biāo)記-整理”算法實(shí)現(xiàn),也就是說不會產(chǎn)生內(nèi)存碎片。
此外,G1 收集器不同于之前的收集器的一個(gè)重要特點(diǎn)是:G1 回收的范圍是整個(gè) Java 堆(包
括新生代,老年代),而前六種收集器回收的范圍僅限于新生代或老年代。
Serial 與 Parallel GC 之間的不同之處?
Serial 與 Parallel 在 GC 執(zhí)行的時(shí)候都會引起 stop-the-world。它們之間主要不同 serial 收集
器是默認(rèn)的復(fù)制收集器,執(zhí)行 GC 的時(shí)候只有一個(gè)線程,而 parallel 收集器使用多個(gè) GC 線程來
執(zhí)行。

浙公網(wǎng)安備 33010602011771號