概述
說起垃圾收集器 (Garbage Collection峦阁,下文簡稱GC)辆飘,Java 內(nèi)存區(qū)域的各個(gè)部分龄捡,其中 程序計(jì)數(shù)器卓嫂、虛擬機(jī)棧、本地方法棧3個(gè)區(qū)域隨線程而生聘殖,隨線程而滅晨雳,棧中的棧幀隨著方法的進(jìn)入和退出而有條不紊執(zhí)行著出棧和入棧操作。每一個(gè)棧幀中分配多少內(nèi)存基本上是在類結(jié)構(gòu)確定下來時(shí)就已知的奸腺,因此 這幾個(gè)區(qū)域的內(nèi)存分配和回收都具備確定性餐禁,在這個(gè)區(qū)域內(nèi)就不需要過多的考慮如何回收的問題,當(dāng)方法結(jié)束或者線程結(jié)束時(shí)突照,內(nèi)存自然就跟隨著回收了帮非。
而 Java 堆和方法區(qū)這兩個(gè)區(qū)域則有不確定性:一個(gè)接口的多個(gè)實(shí)現(xiàn)類需要的內(nèi)存可能是不一樣的,一個(gè)方法所執(zhí)行的不同條件分支所需要的內(nèi)存也可能不一樣,只有處于運(yùn)行區(qū)間末盔,我們才能知道程序究竟會(huì)創(chuàng)建那些對象筑舅,創(chuàng)建多少個(gè)對象,這部分內(nèi)存的分配和回收是動(dòng)態(tài)的庄岖。垃圾收集器 所關(guān)注的正是這部分內(nèi)存該如何管理豁翎。
1. 內(nèi)存分配與回收策略
對象內(nèi)存的分配,都是在堆上分配的
Java 堆是垃圾收集器管理的主要區(qū)域隅忿,因此也被稱作GC 堆(Garbage Collected Heap).從垃圾回收的角度心剥,由于現(xiàn)在收集器基本都采用分代垃圾收集算法,所以 Java 堆還可以細(xì)分為:新生代和老年代:再細(xì)致一點(diǎn)有:Eden 空間背桐、From Survivor优烧、To Survivor 空間等。進(jìn)一步劃分的目的是更好地回收內(nèi)存链峭,或者更快地分配內(nèi)存畦娄。
堆空間的基本結(jié)構(gòu):
如圖所示 Eden 區(qū) 、 From Survivor0 區(qū)弊仪、To Survivor1 區(qū)都屬于新生代熙卡,Old Memory屬于老年代
1.1 對象優(yōu)先在Eden分配
大部分情況,對象都會(huì)首先在 Eden 區(qū)分配励饵,當(dāng) Eden 區(qū)沒有足夠的空間進(jìn)行分配時(shí)驳癌,虛擬機(jī)將發(fā)起一次 Minor GC, 在 一次垃圾回收后如果對象還存活,則會(huì)進(jìn)入 s0 或者 s1 役听,并且對象的年齡還會(huì)加 1 颓鲜,當(dāng)他年齡增加到一定程度(默認(rèn)為15歲),就會(huì)被存放到老年代中典予。對象存放到老年代的年齡閥值可以通過參數(shù) -XX:MaxTenuringThreshold 來設(shè)置甜滨。
案例 :
public class GCTest {
public static void main(String[] args) {
byte[] allocation1, allocation2,allocation3,allocation4,allocation5;
allocation1 = new byte[32000*1024];
allocation2 = new byte[32000*1024];
allocation3 = new byte[1000*1024];
allocation4 = new byte[1000*1024];
allocation5 = new byte[1000*1024];
}
}
GC輸出 :
[GC (Allocation Failure) [PSYoungGen: 37244K->928K(76288K)] 37244K->32936K(251392K), 0.0229665 secs] [Times: user=0.10 sys=0.01, real=0.02 secs]
Heap
PSYoungGen total 76288K, used 36825K [0x000000076ab00000, 0x0000000774000000, 0x00000007c0000000)
eden space 65536K, 54% used [0x000000076ab00000,0x000000076ce0e518,0x000000076eb00000)
from space 10752K, 8% used [0x000000076eb00000,0x000000076ebe8000,0x000000076f580000)
to space 10752K, 0% used [0x0000000773580000,0x0000000773580000,0x0000000774000000)
ParOldGen total 175104K, used 32008K [0x00000006c0000000, 0x00000006cab00000, 0x000000076ab00000)
object space 175104K, 18% used [0x00000006c0000000,0x00000006c1f42010,0x00000006cab00000)
Metaspace used 3138K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 345K, capacity 388K, committed 512K, reserved 1048576K
從輸出可以看出,瘤袖,虛擬機(jī)將發(fā)起一次 Minor GC 衣摩,因?yàn)?from 區(qū)已經(jīng)占用 8% ,老年代已經(jīng) 18% 當(dāng)對象無法存入 survivor 空間捂敌,所以只好通過分配擔(dān)保機(jī)制把新生代的對象提前轉(zhuǎn)移到老年代中去艾扮,老年代的空間足夠放下對象 ,所以不會(huì)出現(xiàn) Full GC黍匾。執(zhí)行玩 Minor GC 后栏渺,后面分配的對象如果能夠存在 Eden 區(qū),還是會(huì)在 Eden區(qū)分配內(nèi)存锐涯。
1.2 大對象直接進(jìn)入老年代
大對象就是指需要大量連續(xù)內(nèi)存空間的 Java 對象磕诊,最典型的大對象便是那種很長的字符串,或者元素?cái)?shù)量龐大的數(shù)組,例如上面的例子 大對象byte[] 數(shù)組 霎终,我們在寫程序的時(shí)候應(yīng)注意避免滞磺。
在 Java 虛擬機(jī)中要避免大對象的原因是,在分配空間時(shí)莱褒,他容易導(dǎo)致內(nèi)存還有不少空間時(shí)击困,就提前觸發(fā)垃圾收集,以獲取足夠的連續(xù)空間才能安置它們广凸,而當(dāng)復(fù)制對象時(shí)阅茶,大對象就意味著高額的內(nèi)存復(fù)制開銷。HotSpot虛擬機(jī)提供了 -XX:PretenureSizeThreshold 參數(shù)谅海,指定大于該設(shè)置值的對象直接在老年代分配脸哀,這樣做的目的就是避免在Eden 區(qū)及兩個(gè) Survivor 區(qū)之間來回復(fù)制,產(chǎn)生大量的復(fù)制操作扭吁。
1.3 長期存活的對象將進(jìn)入老年代
HotSpot 虛擬機(jī)中多數(shù)收集器都采用了分代手機(jī)來管理堆內(nèi)存撞蜂,那內(nèi)存回收時(shí)就必須能決策那些存活對象應(yīng)存放在新生代,你那些存活對象放在老年代中侥袜。為做到這點(diǎn)蝌诡,虛擬機(jī)給每個(gè)對象定義一個(gè)年齡 (Age)計(jì)數(shù)器,存儲(chǔ)在對象頭中枫吧,對象通常在 Eden 區(qū)誕生浦旱,如果經(jīng)過第一次 Minor GC后仍然存活,并且能被 Survivor 容納的話由蘑,該對象會(huì)被移動(dòng)到 Survivor 空間中闽寡,并且將其年齡設(shè)置為 1 歲代兵,對象在 Survivor 區(qū)沒熬過一次 Minor GC, 年齡就增加一歲尼酿,當(dāng)它的年齡增加到一定程度(默認(rèn)15歲)就會(huì)被晉升到老年代中。對象晉升老年代的年齡閾值植影,可以通過參數(shù)-XX: MaxTenuringThreshold設(shè)置裳擎。
1.4 動(dòng)態(tài)對象年齡判定
為了能更好地適應(yīng)不同程序的內(nèi)存狀況,HotSpot 虛擬機(jī)并不是永遠(yuǎn)要求對象的年齡必須達(dá)到 --XX:MaxTenuringThreshold才能晉升老年代思币,如果在 Survivor 空間中相同年齡所有對象大小的總和大于 Survivor 空間的一半鹿响,年齡大于或等于該年齡的對象就可以直接進(jìn)入老年代,無需等到 -XX: MaxTenuringThreshold中要求的年齡谷饿。
案例 :
public class GCTest {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) {
testTenuringThreshold2();
}
/**
* VM參數(shù):-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
* -XX:MaxTenuringThreshold=15
* -XX:+PrintTenuringDistribution
*/
@SuppressWarnings("unused")
public static void testTenuringThreshold2()
{
byte[] allocation1, allocation2, allocation3, allocation4;
allocation1 = new byte[_1MB / 4];
// allocation1+allocation2大于survivo空間一半
allocation2 = new byte[_1MB / 4];
allocation3 = new byte[4 * _1MB];
allocation4 = new byte[4 * _1MB];
allocation4 = null;
allocation4 = new byte[4 * _1MB]; }
}
執(zhí)行結(jié)果:
Heap
PSYoungGen total 9216K, used 7541K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
eden space 8192K, 92% used [0x00000007bf600000,0x00000007bfd5d738,0x00000007bfe00000)
from space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
to space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
ParOldGen total 10240K, used 8192K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
object space 10240K, 80% used [0x00000007bec00000,0x00000007bf400020,0x00000007bf600000)
Metaspace used 3113K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 342K, capacity 388K, committed 512K, reserved 1048576K
1.5 進(jìn)行GC的區(qū)域
針對 Hotspot VM 的實(shí)現(xiàn)惶我,它里面的GC可以分為兩大類
部分收集(Partial GC):
- 新生代收集(Minor GC / Young GC):只對新生代進(jìn)行垃圾收集
- 老年代收集(Major GC / Old GC):只對老年代進(jìn)行垃圾收集
- 混合手機(jī)(Mixed GC):對整合新生代和部分老年代進(jìn)行垃圾收集
整堆收集 :
- Full GC : 收集整個(gè) Java 堆和方法區(qū)。
2. 如何判斷對象已死博投?
在堆里面存放著 Java 世界幾乎所有的對象實(shí)例绸贡,垃圾收集器在對堆進(jìn)行回收前,第一件事情就是要確定哪些對象還 "存活" 著,哪些對象已經(jīng) "死去"听怕。
2.1 引用計(jì)數(shù)法
很多教科書判斷對象是否存活的算法是這樣的 : 在對象中添加一個(gè)引用計(jì)數(shù)器捧挺,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器值就加一尿瞭;當(dāng)引用失效時(shí)闽烙,計(jì)數(shù)器值就減一;任何時(shí)刻計(jì)數(shù)器為零的對象就是不可能被使用的声搁。
客觀的說黑竞,引用計(jì)數(shù)算法(Reference Counting)雖然占用了一些額外的內(nèi)存空間來進(jìn)行計(jì)數(shù),但 它的原理簡單疏旨,判定效率也很高摊溶,在大多數(shù)情況下它都是一個(gè)不錯(cuò)的算法。但是充石,在Java 領(lǐng)域莫换,至少主流的 Java 虛擬機(jī)里面都沒有選用引用計(jì)數(shù)算法進(jìn)行管理內(nèi)存,主要原因是骤铃,這個(gè)看似簡單的算法有很多例外的情況要考慮拉岁,必須配合大量額外處理才能保證正確的工作,譬如單純的引用計(jì)數(shù) 就很難解決對象之間相互循環(huán)引用的問題惰爬。
/**
* testGC()方法執(zhí)行后喊暖,objA和objB會(huì)不會(huì)被GC呢?
*/
public class ReferenceCountingGc {
Object instance = null;
public static void main(String[] args) {
ReferenceCountingGc objA = new ReferenceCountingGc();
ReferenceCountingGc objB = new ReferenceCountingGc();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
// 假設(shè)在這行發(fā)生GC撕瞧,objA和objB是否能被回收陵叽?
System.gc();
}
}
2.2 可達(dá)性分析
這個(gè)算法的基本思想就是通過一系列的稱為 “GC Roots” 的對象作為起點(diǎn),從這些接待您開始向下搜索丛版,節(jié)點(diǎn)所走過的路徑稱為引用鏈巩掺,當(dāng)一個(gè)對象到 GC Roots 沒有任何引用鏈相連的話,則證明此對象是不可用的页畦。
可作為 GC Roots 的對象包括:
- 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象
- 本地方法棧(Native 方法)中引用對象
- 方法區(qū)中類靜態(tài)屬性引用對象
- 方法區(qū)中常量引用的對象
- 所有被同步鎖持有的對象
2.3 引用
無論是通過引用計(jì)數(shù)算法判斷對象的引用數(shù)量胖替,還是通過可達(dá)性分析算法判斷對象是否引用鏈可達(dá),判斷對象是否存活都和 “引用” 離不開關(guān)系 豫缨。
在 JDK1.2 之前独令,Java 里的引用還是很傳統(tǒng)的定義:如果 reference 類型的數(shù)據(jù)中存儲(chǔ)的數(shù)值代表的是另外一塊內(nèi)存起始地址,就稱該reference數(shù)據(jù)是代表 某塊內(nèi)存好芭、某個(gè)對象的引用燃箭。這種定義并沒有什么不對,只是現(xiàn)在看來有些過于狹隘了舍败,一個(gè)對象在這種定義下只有 "被引用" 或者 "未被引用" 兩種狀態(tài)招狸,對于描述一些 "食之無味碗硬,棄之可惜" 的對象就顯的無能為力了,譬如我們希望能描述一類對象:當(dāng)內(nèi)存空間還足夠時(shí)瓢颅,能保留在內(nèi)存之中恩尾,如果內(nèi)存空 間在進(jìn)行垃圾收集后仍然非常緊張,那就可以拋棄這些對象——很多系統(tǒng)的緩存功能都符合這樣的應(yīng) 用場景挽懦。
在 JDK1.2 之后翰意,Java 對引用的概念進(jìn)行了擴(kuò)充,將引用分為強(qiáng)引用(Strongly Re-ference)信柿、軟 引用(Soft Reference)冀偶、弱引用(Weak Reference)和虛引用(Phantom Reference)4種,這4種引用強(qiáng) 度依次逐漸減弱渔嚷。
-
強(qiáng)引用
強(qiáng)引用是最傳統(tǒng)的 "引用" 的定義 进鸠,是指在程序代碼之中普遍存在的引用賦值,即類似“Object obj=new Object()”這種引用關(guān)系形病。無論任何情況下客年,只要強(qiáng)引用關(guān)系還存在,垃圾收集器就永遠(yuǎn)不會(huì)回 收掉被引用的對象漠吻。
-
軟引用
軟引用是用來描述一些還有用量瓜,但非必要的對象。只被軟引用關(guān)聯(lián)著的對象途乃,在系統(tǒng)將要發(fā)生內(nèi)存溢出錢绍傲,會(huì)把這些對象列進(jìn)回收范圍之中進(jìn)行二次回收,如果這次回收還沒有足夠的內(nèi)存耍共,才會(huì)拋出內(nèi)存溢出異常烫饼。在 JDK 1.2 之后提供了 SoftReference 類實(shí)現(xiàn)軟引用。
-
弱引用
弱引用也是用來描述哪些非必需對象试读,但是它的強(qiáng)度比軟引用更弱一些杠纵,被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾收集發(fā)生為止。當(dāng)垃圾收集器開始工作鹏往,無論當(dāng)前內(nèi)存是否足夠淡诗,都會(huì)回收之被弱引用的關(guān)聯(lián)的對象骇塘。在JDK 1.2版之后提供了WeakReference類來實(shí)現(xiàn)弱引用伊履。
-
虛引用
虛引用也稱為 "幽靈引用" 或者 "幻影引用" ,它是最弱的一種引用關(guān)系款违。一個(gè)對象是否有需引用的存在唐瀑,完全不會(huì)對其生存時(shí)間構(gòu)成影響,也無法通過虛引用來去的一個(gè)對象實(shí)例插爹。為一個(gè)對象設(shè)置虛引用關(guān)聯(lián)的唯一目的只是為了能在這個(gè)對象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知哄辣。在JDK 1.2版之后提供 了PhantomReference類來實(shí)現(xiàn)虛引用请梢。
2.4 對象生存還是死亡
即使在可達(dá)性分析法中不可達(dá)達(dá)對象,也并非是 "非死不可" 的力穗,這個(gè)時(shí)候它們處于 "緩刑階段"毅弧,要真正宣告一個(gè)對象死亡,至少要經(jīng)歷兩次經(jīng)歷過程当窗;可達(dá)性分析法中不可達(dá)的對象唄第一次標(biāo)記并且進(jìn)行篩選够坐,篩選的條件是此對象是否有必要執(zhí)行 finalize 方法。當(dāng)對象沒有覆蓋 finalize 方法崖面,或 finalize 方法已經(jīng)被虛擬機(jī)調(diào)用過時(shí)元咙,虛擬機(jī)將這兩種情況視為沒有必要執(zhí)行。
被判定為需要執(zhí)行的對象將會(huì)被放在一個(gè)隊(duì)列中進(jìn)行第二次標(biāo)記巫员,除非這個(gè)對象與引用鏈上的任何一個(gè)對象建立關(guān)聯(lián)庶香,否則就會(huì)被真的回收。
2.5 回收方法區(qū)
方法區(qū)的垃圾收集主要分為兩部分內(nèi)容:廢棄的常量和不再使用的類型简识「弦矗回收廢棄常量與回收 Java堆中的對象非常類似。
- 廢棄的常量
廢棄的常量 :假如一個(gè)字符串 "Java" 曾經(jīng)進(jìn)入常量池 中七扰,但是當(dāng)前系統(tǒng)又沒有任何一個(gè)字符串對象的值是 "Java"倘零,換句話,已經(jīng)沒有任何字符串對象引用常量池的 "Java" 戳寸,且虛擬機(jī)中也沒有其他地方引用這個(gè)字面量呈驶。如果在這時(shí)發(fā)生內(nèi)存回收,而且垃圾收集器判斷確有必要的話疫鹊,這個(gè) "Java" 常量就會(huì)被系統(tǒng)清理出常量池袖瞻。常量池其他類 (接口) 、方法拆吆、字段的符號引用也與此類似聋迎。
- 不在使用的類型
判定一個(gè)常量是否“廢棄”還是相對簡單,而要判定一個(gè)類型是否屬于“不再被使用的類”的條件就 比較苛刻了枣耀。需要同時(shí)滿足下面三個(gè)條件:
- 該類所有的實(shí)例都已經(jīng)被回收霉晕,也就是Java堆中不存在該類及其任何派生子類的實(shí)例。
- 加載該類的類加載器已經(jīng)被回收捞奕,這個(gè)條件除非是經(jīng)過精心設(shè)計(jì)的可替換類加載器的場景牺堰,如 OSGi、JSP的重加載等颅围,否則通常是很難達(dá)成的伟葫。
- 該類對應(yīng)的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方 法院促。
虛擬機(jī)可以對滿足上述 3 個(gè)條件的無用類進(jìn)行回收筏养,這里說的僅僅是“可以”斧抱,而并不是和對象一樣不使用了就會(huì)必然被回收。
3. 垃圾收集算法
3.1 分代收集算法
當(dāng)前商業(yè)虛擬機(jī)的垃圾收集器渐溶,大多數(shù)都遵循了 "分代收集" (Generational Collection)都理論進(jìn)行設(shè)計(jì)的辉浦,分代收集名為理論,實(shí)質(zhì)是一套符合大多數(shù)程序運(yùn)行實(shí)際情況的經(jīng)驗(yàn)法則茎辐,它建立在兩個(gè)分代假說之上
- 弱分代假說(Weak Generational Hypothesis):絕大多數(shù)對象都是朝生夕滅的盏浙。
- 強(qiáng)分代假說(Strong Generational Hypothesis):熬過越多次垃圾收集過程的對象就越難以消 亡。
這兩個(gè)分代假說共同奠定了多款常用的垃圾收集器的一致設(shè)計(jì)原則 : 收集器應(yīng)該將 Java 堆劃分出不同的區(qū)域荔茬,然后將回收對象依據(jù)其年齡(年齡即對象熬過垃圾收集過程的次數(shù))分配到不同的區(qū)域存儲(chǔ)废膘。顯而易見,如果一個(gè)區(qū)域中大多數(shù)對象都是朝生夕滅慕蔚,難以熬過垃圾回收的話丐黄,那么把它們集中放在一起,每次回收時(shí)只關(guān)注如何保留少量存活而不是標(biāo)記哪些大量將要被回收的對象孔飒,就能以較低代價(jià)回收到大量的空間如果剩下的都是難以消亡的對象灌闺,那把它們集中放在一塊, 虛擬機(jī)便可以使用較低的頻率來回收這個(gè)區(qū)域坏瞄,這就同時(shí)兼顧了垃圾收集的時(shí)間開銷和內(nèi)存的空間有 效利用桂对。
設(shè)計(jì)者一般至少會(huì)把Java堆劃分為新生代 (Young Generation)和老年代(Old Generation)兩個(gè)區(qū)域[2]。顧名思義鸠匀,在新生代中蕉斜,每次垃圾收集 時(shí)都發(fā)現(xiàn)有大批對象死去,而每次回收后存活的少量對象缀棍,將會(huì)逐步晉升到老年代中存放
3.2 標(biāo)記清除算法
最早出現(xiàn)也是最基礎(chǔ)的垃圾算法是 "標(biāo)記-清除" (Mark-Sweep)算法宅此,如圖它的名字一樣,算法分為 "標(biāo)記" 和 "清除" 兩個(gè)階段 :首先標(biāo)記出所有需要回收的對象爬范,在標(biāo)記完成后父腕,統(tǒng)一回收掉所有被標(biāo)記的對象,也可以返過來青瀑,標(biāo)記存活的對象璧亮,統(tǒng)一回收所有未標(biāo)記的對象。標(biāo)記過程就是對象是否屬于垃圾的判定過程
這種垃圾算法會(huì)帶來2個(gè)明顯的問題
- 效率問題
- 空間問題 (標(biāo)記清除后產(chǎn)生大量不連續(xù)的碎片)
3.3 標(biāo)記-復(fù)制算法
標(biāo)記-復(fù)制算法常被簡稱為復(fù)制算法斥难,為了解決標(biāo)記-清除算法面對大量可回收對象時(shí)執(zhí)行效率低的問題枝嘶,"標(biāo)記-復(fù)制" 收集算法出現(xiàn)了。它可以將內(nèi)存分為大小相同的兩塊蘸炸,每次使用其中一塊躬络。當(dāng)這一塊的內(nèi)存使用完后,就將還存活的對象復(fù)制到另一塊去搭儒,然后再把使用的空間一次清理掉穷当。這樣就使每次的內(nèi)存回收都是對內(nèi)存區(qū)間的一半進(jìn)行回收。
3.4 標(biāo)記-整理算法
標(biāo)記-復(fù)制算法在對象存活率較高時(shí)就要進(jìn)行較多的復(fù)制操作淹禾,效率將會(huì)降低馁菜。更關(guān)鍵的是,如果 不想浪費(fèi)50%的空間铃岔,就需要有額外的空間進(jìn)行分配擔(dān)保汪疮,以應(yīng)對被使用的內(nèi)存中所有對象都100%存 活的極端情況,所以在老年代一般不能直接選用這種算法毁习。
針對老年代對象的存亡特征智嚷,提出了另外一種有針對性的“標(biāo)記-整 理”(Mark-Compact)算法,其中的標(biāo)記過程仍然與“標(biāo)記-清除”算法一樣纺且,但后續(xù)步驟不是直接對可 回收對象進(jìn)行清理盏道,而是讓所有存活的對象都向內(nèi)存空間一端移動(dòng),然后直接清理掉邊界以外的內(nèi) 存载碌。
4. 垃圾收集器
如果說收集算法時(shí)內(nèi)存回收的方法論猜嘱,那么垃圾收集器就是內(nèi)存回收實(shí)踐者。
雖然我們會(huì)對各個(gè)收集器進(jìn)行比 較嫁艇,但并非為了挑選一個(gè)最好的收集器出來,雖然垃圾收集器的技術(shù)在不斷進(jìn)步,但直到現(xiàn)在還沒有 最好的收集器出現(xiàn)瞒瘸,更加不存在“萬能”的收集器拳锚,所以我們選擇的只是對具體應(yīng)用最合適的收集器。
4.1 Serial收集器
Serial 收集器是最基礎(chǔ)猾漫、歷史最悠久的收集器纯丸,在 JDK1.3之前 是HotSpot 虛擬機(jī)新生代收集器唯一選擇。這個(gè)收集器是一個(gè)單線程工作的收集器静袖,但它的“單線 程”的意義并不僅僅是說明它只會(huì)使用一個(gè)處理器或一條收集線程去完成垃圾收集工作觉鼻,更重要的是強(qiáng) 調(diào)在它進(jìn)行垃圾收集時(shí),必須暫停其他所有工作線程队橙,直到它收集結(jié)束坠陈。
虛擬機(jī)的設(shè)計(jì)者們當(dāng)然知道 Stop The World 帶來的不良用戶體驗(yàn),所以在后續(xù)的垃圾收集器設(shè)計(jì)中停頓時(shí)間在不斷縮短(仍然還有停頓捐康,尋找最優(yōu)秀的垃圾收集器的過程仍然在繼續(xù))仇矾。
但是 Serial 收集器有沒有優(yōu)于其他垃圾收集器的地方呢?當(dāng)然有解总,它簡單而高效(與其他收集器的單線程相比)贮匕。Serial 收集器由于沒有線程交互的開銷,自然可以獲得很高的單線程收集效率花枫。Serial 收集器對于運(yùn)行在 Client 模式下的虛擬機(jī)來說是個(gè)不錯(cuò)的選擇刻盐。
4.2 ParNew收集器
ParNew收集器實(shí)質(zhì)上是 Serial收集器的多線程并行版本掏膏,除了同時(shí)使用多條線程進(jìn)行垃圾收集之外其余的行為包括Serial收集器可用的所有控制參數(shù)(例如:-XX:SurvivorRatio、-XX: PretenureSizeThreshold敦锌、-XX:HandlePromotionFailure等)馒疹、收集算法、Stop The World乙墙、對象分配規(guī) 則颖变、回收策略等都與Serial收集器完全一致
ParNew收集器除了支持多線程并行收集之外,尤其是JDK 7之前的遺留系統(tǒng)中首選的新生代收集 器听想,其中有一個(gè)與功能腥刹、性能無關(guān)但其實(shí)很重要的原因是:除了Serial收集器外,目前只有它能與CMS 收集器配合工作汉买。
4.3 Parallel Scavenge收集器
Parallel Scavenge收集器也是一款新生代收集器, 它同樣是基于標(biāo)記-復(fù)制算法實(shí)現(xiàn)的收集器衔峰,也是能夠并行收集的多線程收集器
- Parallel Scavenge 和 ParNew 特別之處
-XX:+UseParallelGC
使用 Parallel 收集器+ 老年代串行
-XX:+UseParallelOldGC
使用 Parallel 收集器+ 老年代并行
由于與吞吐量關(guān)系密切,Parallel Scavenge收集器也經(jīng)常被稱作“吞吐量優(yōu)先收集器”录别。Parallel Scavenge 收集器關(guān)注點(diǎn)是吞吐量(高效率的利用 CPU)朽色。CMS等垃圾收集器關(guān)注點(diǎn)更多的是用戶線程停頓時(shí)間(提高用戶體驗(yàn))。所謂吞吐量就是 CPU 用于運(yùn)行用戶代碼的時(shí)間與 CPU 總小號時(shí)間的比值组题。
Parallel Scavenge 收集器提供了很多參數(shù)供用戶找到最合適的停頓時(shí)間或最大吞吐量葫男,如果對于收集器運(yùn)作不太了解,手工優(yōu)化存在困難的時(shí)候崔列,使用 Parallel Scavenge 收集器配合自適應(yīng)調(diào)節(jié)策略梢褐,把內(nèi)存管理優(yōu)化交給虛擬機(jī)去完成也是一個(gè)不錯(cuò)的選擇。這種重中之重重中之重
這是 JDK1.8 默認(rèn)收集器
使用 java -XX:+PrintCommandLineFlags -version 命令查看
-XX:InitialHeapSize=262921408 -XX:MaxHeapSize=4206742528 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
java version "1.8.0_211"
Java(TM) SE Runtime Environment (build 1.8.0_211-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.211-b12, mixed mode)
JDK1.8 默認(rèn)使用的是 Parallel Scavenge + Parallel Old赵讯,如果指定了-XX:+UseParallelGC 參數(shù)盈咳,則默認(rèn)指定了-XX:+UseParallelOldGC,可以使用-XX:-UseParallelOldGC 來禁用該功能
4.4 Seria Old 收集器
Serial Old是Serial收集器的老年代版本边翼,它同樣是一個(gè)單線程收集器鱼响。它主要有兩大用途:一種用途是在 JDK1.5 以及以前的版本中與 Parallel Scavenge 收集器搭配使用,另一種用途是作為 CMS 收集器的后備方案组底。
4.5 Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本丈积,支持多線程并發(fā)收集,基于標(biāo)記-整理算法實(shí)現(xiàn)债鸡。在注重吞吐量以及 CPU 資源的場合江滨,都可以優(yōu)先考慮 Parallel Scavenge 收集器和 Parallel Old 收集器。
4.6 CMS收集器
CMS (Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器厌均。目前很大一部分的 Java 應(yīng)用集在互聯(lián)網(wǎng)網(wǎng)站或者基于瀏覽器的 B/S 系統(tǒng)服務(wù)器上唬滑,這類應(yīng)用通常都會(huì)較為 關(guān)注服務(wù)的響應(yīng)速度,希望系統(tǒng)停頓時(shí)間盡可能短,以給用戶帶來良好的交互體驗(yàn)晶密。CMS收集器就非 常符合這類應(yīng)用的需求擒悬。
從名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于標(biāo)記-清除算法實(shí)現(xiàn)的,它的運(yùn)作 過程相對于前面幾種收集器來說要更復(fù)雜一些惹挟,整個(gè)過程分為四個(gè)步驟茄螃,包括:
- 初始標(biāo)記(CMS initial mark):暫停所有的其他線程缝驳,并記錄下直接與 root 相連的對象连锯,速度很快
- 并發(fā)標(biāo)記(CMS concurrent mark):從GC roots的直接關(guān)聯(lián)對象開始遍歷整個(gè)對象圖的過程, 這個(gè)過程耗時(shí)較長但是不需要停頓用戶線程,可以與卡機(jī)手機(jī)線程一起并發(fā)運(yùn)行
- 重新標(biāo)記(CMS remark):為了修正并發(fā)標(biāo)記期間用狱,因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對象的標(biāo)記記錄运怖,這個(gè)階段的停頓時(shí)間通常會(huì)比初始標(biāo)記階段稍長一 些,但也遠(yuǎn)比并發(fā)標(biāo)記階段的時(shí)間短
- 并發(fā)清除(CMS concurrent sweep):強(qiáng)力刪除掉標(biāo)記階段判斷的已經(jīng)死亡的對象夏伊,由于不需要移動(dòng)存活對象摇展,所以整個(gè)階段也是可以與用戶線程同時(shí)并發(fā)的。
從它的名字就可以看出它是一款優(yōu)秀的垃圾收集器溺忧,主要優(yōu)點(diǎn):并發(fā)收集咏连、低停頓。但是它有下面三個(gè)明顯的缺點(diǎn):
- 對 CPU 資源敏感
- 無法處理浮動(dòng)垃圾
- 它使用的回收算法-“標(biāo)記-清除”算法會(huì)導(dǎo)致收集結(jié)束時(shí)會(huì)有大量空間碎片產(chǎn)生鲁森。
4.6 Garbage First收集器
Garbage First(簡稱G1)收集器是垃圾收集器技術(shù)發(fā)展歷史上的里程碑式的成果祟滴,它開創(chuàng)了收集 器面向局部收集的設(shè)計(jì)思路和基于Region的內(nèi)存布局形式。
G1 (Garbage-First) 是一款面向服務(wù)器的垃圾收集器,主要針對配備多顆處理器及大容量內(nèi)存的機(jī)器. 以極高概率滿足 GC 停頓時(shí)間要求的同時(shí),還具備高吞吐量性能特征
- 并發(fā)與并行 : G1 能充分利用 CPU歌溉、多核環(huán)境下的硬件優(yōu)勢垄懂,使用多個(gè) CPU(CPU 或者 CPU 核心)來縮短 Stop-The-World 停頓時(shí)間。部分其他收集器原本需要停頓 Java 線程執(zhí)行的 GC 動(dòng)作痛垛,G1 收集器仍然可以通過并發(fā)的方式讓 java 程序繼續(xù)執(zhí)行草慧。
- 分代收集 : 雖然 G1 可以不需要其他收集器配合就能獨(dú)立管理整個(gè) GC 堆,但是還是保留了分代的概念匙头。
- 空間整合 : 與 CMS 的“標(biāo)記-清理”算法不同漫谷,G1 從整體來看是基于“標(biāo)記-整理”算法實(shí)現(xiàn)的收集器;從局部上來看是基于“標(biāo)記-復(fù)制”算法實(shí)現(xiàn)的蹂析。
- 可預(yù)測的停頓 : 這是 G1 相對于 CMS 的另一個(gè)大優(yōu)勢舔示,降低停頓時(shí)間是 G1 和 CMS 共同的關(guān)注點(diǎn),但 G1 除了追求低停頓外识窿,還能建立可預(yù)測的停頓時(shí)間模型斩郎,能讓使用者明確指定在一個(gè)長度為 M 毫秒的時(shí)間片段內(nèi)。
G1 收集器的運(yùn)作大致分為以下幾個(gè)步驟:
- 初始標(biāo)記
- 并發(fā)標(biāo)記
- 最終標(biāo)記
- 篩選回收
G1 收集器在后臺(tái)維護(hù)了一個(gè)優(yōu)先列表喻频,每次根據(jù)允許的收集時(shí)間缩宜,優(yōu)先選擇回收價(jià)值最大的 Region(這也就是它的名字 Garbage-First 的由來) 。這種使用 Region 劃分內(nèi)存空間以及有優(yōu)先級的區(qū)域回收方式,保證了 G1 收集器在有限時(shí)間內(nèi)可以盡可能高的收集效率(把內(nèi)存化整為零)锻煌。
4.6 ZGC收集器
ZGC(Z Garbage Collector)是一款在 JDK 11中新加入的具有實(shí)驗(yàn)性質(zhì)[1]的低延遲垃圾收集器妓布,是由Oracle公司研發(fā)的。2018年Oracle創(chuàng)建了 JEP 333將ZGC提交給OpenJDK宋梧,推動(dòng)其進(jìn)入OpenJDK 11的發(fā)布清單之中匣沼。
與 CMS 中的 ParNew 和 G1 類似,ZGC 也采用標(biāo)記-復(fù)制算法捂龄,不過 ZGC 對該算法做了重大改進(jìn)释涛。
ZGC運(yùn)作過程:
- 并發(fā)標(biāo)記
- 并發(fā)預(yù)備重分配
- 并發(fā)重分配
- 并發(fā)重映射
個(gè)人博客地址:http://blog.yanxiaolong.cn | <font color = "orange"> 『縱有疾風(fēng)起,人生不言棄』 </font>