JVM的垃圾回收主要針對(duì) “堆” 和 “方法區(qū)”,主要解決以下問(wèn)題:
- 哪些內(nèi)存需要回收磺箕?(對(duì)象存活判定算法)
- 觸發(fā)垃圾回收的條件奖慌?
- 如何回收?(垃圾收集算法)
- 用什么回收松靡?(垃圾收集器)
本文先介紹 對(duì)象存活判定算法简僧、垃圾收集算法,然后介紹常見(jiàn)的 JVM 垃圾收集器雕欺。
1. 對(duì)象存活判定算法
一般來(lái)說(shuō)岛马,判定對(duì)象是否可以被回收有許多算法,目前主流的算法有 引用計(jì)數(shù)法阅茶、可達(dá)性分析法蛛枚,本節(jié)對(duì)這兩種算法進(jìn)行簡(jiǎn)要介紹。
1.1 引用計(jì)數(shù)法
每個(gè)對(duì)象維護(hù)一個(gè)引用計(jì)數(shù)器脸哀。當(dāng)有一個(gè)新的引用指向該對(duì)象時(shí)蹦浦,引用計(jì)數(shù)器就+1;當(dāng)指向該引用對(duì)象失效時(shí)撞蜂,計(jì)數(shù)器就-1盲镶;當(dāng)引用計(jì)數(shù)器值為0時(shí),說(shuō)明該對(duì)象可以被回收蝌诡。
JVM 沒(méi)有選用 引用計(jì)數(shù)法 來(lái)管理內(nèi)存溉贿,其主要原因就是引用計(jì)數(shù)法很難解決對(duì)象之間的循環(huán)引用問(wèn)題。
循環(huán)引用示例:
public class MyObject {
public Object ref = null;
public static void main(String[] args) {
MyObject myObject1 = new MyObject();
MyObject myObject2 = new MyObject();
myObject1.ref = myObject2;
myObject2.ref = myObject1;
myObject1 = null;
myObject2 = null;
}
}
如上圖所示浦旱,白色框代表在堆中開(kāi)辟了一個(gè)內(nèi)存空間宇色,黃色和藍(lán)色線(xiàn)代表引用,各位兩條,也就代表計(jì)數(shù)器各為2宣蠕,myObject1 和 myObject2 分別指向 null例隆,線(xiàn)各自消失一條,計(jì)數(shù)器減1抢蚀,但還剩1镀层,也就如圖中ref的指向,沒(méi)有恢復(fù)0皿曲,所以不會(huì)被回收唱逢。
1.2 可達(dá)性分析法
目前主流的JVM都采用可達(dá)性分析算法來(lái)判定對(duì)象是否存活。
可達(dá)性分析法:通過(guò)"GC Roots"對(duì)象(必須活躍的引用)作為起點(diǎn)屋休,從起點(diǎn)開(kāi)始進(jìn)行圖的遍歷坞古,所走過(guò)的路徑稱(chēng)為引用鏈。當(dāng)一個(gè)對(duì)象不在引用鏈上時(shí)博投,則該對(duì)象被判定為可回收對(duì)象绸贡。
上圖 Object 5 盯蝴、6 毅哗、7 會(huì)被回收。
可以作為“GC Roots”的對(duì)象包括以下幾種:
- 虛擬機(jī)棧 中引用的對(duì)象捧挺;
比如正在運(yùn)行的線(xiàn)程的虛擬機(jī)棧上的引用變量虑绵。 - 本地方法棧中JNI(Native方法)的引用的對(duì)象;
- 方法區(qū)中的類(lèi)靜態(tài)屬性引用的對(duì)象闽烙;
- 方法區(qū)中的常量引用的對(duì)象翅睛。
1.3 Java引用類(lèi)型
怎么樣的對(duì)象算沒(méi)有被引用?Java語(yǔ)言其實(shí)一共定義了4種引用黑竞,按強(qiáng)度從大到小依次是:
強(qiáng)引用(Strong Reference)
強(qiáng)引用是使用最普遍的引用捕发,GC永遠(yuǎn)不會(huì)回收被強(qiáng)引用的對(duì)象。
例:Object obj=new Object();
軟引用(Soft Reference)
軟引用的對(duì)象很魂,只有在內(nèi)存不足時(shí)扎酷,GC會(huì)對(duì)其回收。
也就是說(shuō)遏匆,只有發(fā)生 OOM 之前的 GC法挨,才會(huì)對(duì)軟引用的對(duì)象進(jìn)行回收
軟引用有時(shí)被用來(lái)實(shí)現(xiàn)內(nèi)存敏感的高速緩存。
例:
// 獲取對(duì)象并緩存
SoftReference softRef = new SoftReference(new SomeClass());
// 從軟引用中獲取對(duì)象
Object cache = softRef.get();
if (cache == null) {
// 當(dāng)軟引用被回收后重新獲取對(duì)象
cache = new SomeClass();
}
return (SomeClass)cache;
弱引用(Weak Reference)
弱引用的對(duì)象擁有更短暫的生命周期幅聘。弱引用與軟引用的區(qū)別在于:在GC進(jìn)行存活對(duì)象判定時(shí)凡纳,一旦發(fā)現(xiàn)了只具有弱引用的對(duì)象,不管當(dāng)前內(nèi)存空間足夠與否帝蒿,都會(huì)回收它荐糜。
Car car = new Car();
WeakReference<Car> weakCar = new WeakReference<Car>(car);
//假設(shè)此處進(jìn)行了一次GC
System.gc()
//打印true
System.out.println(weakCar.get()==null);
虛引用(Phantom Reference)
虛引用是最弱的一種引用關(guān)系。
2. 垃圾回收觸發(fā)條件
由于對(duì)象進(jìn)行了分代處理,因此垃圾回收區(qū)域暴氏、時(shí)間也不一樣丛版。GC有兩種類(lèi)型:Scavenge GC和Full GC。對(duì)于一個(gè)擁有終結(jié)方法的對(duì)象偏序,在垃圾收集器釋放對(duì)象前必須執(zhí)行終結(jié)方法页畦。但是當(dāng)垃圾收集器第二次收集這個(gè)對(duì)象時(shí)便不會(huì)再次調(diào)用終結(jié)方法。
Minor GC
一般情況下研儒,當(dāng)新對(duì)象生成豫缨,并且在Eden申請(qǐng)空間失敗時(shí),就會(huì)觸發(fā)Minor GC端朵,對(duì)Eden區(qū)域進(jìn)行GC好芭,清除非存活對(duì)象,并且把尚且存活的對(duì)象移動(dòng)到Survivor區(qū)冲呢,然后整理Survivor的兩個(gè)區(qū)舍败。這種方式的GC是對(duì)新生代的Eden區(qū)進(jìn)行,不會(huì)影響到老年代敬拓。因?yàn)榇蟛糠謱?duì)象都是從Eden區(qū)開(kāi)始的邻薯,同時(shí)Eden區(qū)不會(huì)分配的很大,所以Eden區(qū)的GC會(huì)頻繁進(jìn)行乘凸。
Full GC
對(duì)整個(gè)堆進(jìn)行整理厕诡,包括年輕代、老年代 和 永久代营勤。Full GC因?yàn)樾枰獙?duì)整個(gè)對(duì)進(jìn)行回收灵嫌,所以比 Scavenge GC 要慢,因此應(yīng)該盡可能減少 Full GC的次數(shù)葛作。在對(duì)JVM調(diào)優(yōu)的過(guò)程中寿羞,很大一部分工作就是對(duì)于 Full GC 的調(diào)節(jié)。有如下原因可能導(dǎo)致Full GC:
老年代(Tenured)被寫(xiě)滿(mǎn)
永久代(Perm)被寫(xiě)滿(mǎn)
System.gc() 被 顯示 調(diào)用
3. 垃圾收集算法
3.1 標(biāo)記-清除算法
標(biāo)記-清除(Mark-Sweep)算法赂蠢,分為 標(biāo)記 和 清除 階段:首先標(biāo)記出所有需要回收的對(duì)象绪穆,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象。
缺點(diǎn):
- 效率問(wèn)題客年;
標(biāo)記和清除兩個(gè)過(guò)程的效率都不高 - 空間問(wèn)題霞幅;
標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會(huì)導(dǎo)致以后在程序運(yùn)行過(guò)程中需要分配較大的對(duì)象時(shí)量瓜,無(wú)法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動(dòng)作司恳。
3.2 復(fù)制算法
復(fù)制(Copying)算法,將內(nèi)存分為相等的兩塊绍傲,每次只使用一塊扔傅。當(dāng)這塊內(nèi)存用光耍共,就將還存活的對(duì)象復(fù)制到另外一塊,然后再把已經(jīng)使用過(guò)的空間一次清理掉猎塞。
優(yōu)點(diǎn):內(nèi)存分配/清除的效率都很高试读,不用考慮內(nèi)存碎片等情況。
缺點(diǎn):內(nèi)存縮小為原來(lái)的一半荠耽,空間利用率低钩骇。
現(xiàn)代商用JVM一般采用復(fù)制算法來(lái)回收新生代,因?yàn)樾律写蟛糠謱?duì)象都是朝生夕死的铝量。實(shí)際一般按照8:1:1的比例將內(nèi)存空間劃分為一塊Eden區(qū)(80%)和兩塊Survivor區(qū)(各10%)倘屹。
3.3 標(biāo)記整理算法
標(biāo)記-整理(Mark-Compact)算法,標(biāo)記過(guò)程和標(biāo)記清除算法一樣慢叨,但后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行清理纽匙,而是讓所有存活的對(duì)象都向一端移動(dòng),然后直接清理掉端邊界以外的內(nèi)存拍谐。
現(xiàn)代商用JVM一般采用標(biāo)記-整理算法回收老年代烛缔。因?yàn)槔夏甏鷮?duì)象存活率高,一般只需要清除少量對(duì)象轩拨。
3.4 分代收集算法
目前大部分JVM都采用的分代收集(Generational Collection)算法践瓷。它的核心思想是根據(jù)對(duì)象存活的生命周期將內(nèi)存劃分為若干個(gè)不同的區(qū)域,不同區(qū)域采用不同的垃圾回收算法气嫁。
一般情況下將堆區(qū)劃分為老年代(Tenured Generation)当窗、新生代(Young Generation)、永久代寸宵。
新生代:對(duì)象朝生暮死,存活率低元咙,一般選用“復(fù)制算法”梯影;
老年代:對(duì)象存活率高、沒(méi)有額外空間對(duì)對(duì)象進(jìn)行分配擔(dān)保庶香,一般采用“標(biāo)記-整理”算法甲棍。
4、JVM垃圾收集器
收集器名稱(chēng) | 介紹 | 使用時(shí)期 | 算法 |
---|---|---|---|
Serial | 單線(xiàn)程收集器赶掖,暫停所有線(xiàn)程 | 新生代 | 復(fù)制算法 |
Serial Old | 單線(xiàn)程收集器感猛,暫停所有線(xiàn)程 | 老年代 | 標(biāo)記-整理算法 |
ParNew | 多線(xiàn)程收集器,與用戶(hù)線(xiàn)程并發(fā) | 新生代 | 復(fù)制算法 |
Parallel Scavenge | 多線(xiàn)程收集器奢赂,與用戶(hù)線(xiàn)程并發(fā)陪白,可控制吞吐量 | 新生代 | 復(fù)制算法 |
Parallel Old | 多線(xiàn)程收集器,與用戶(hù)線(xiàn)程并發(fā) | 老年代 | 標(biāo)記-整理算法 |
CMS | 多線(xiàn)程收集器膳灶,與用戶(hù)線(xiàn)程并發(fā)咱士;對(duì)CPU資源敏感立由;標(biāo)記階段,用戶(hù)線(xiàn)程依舊運(yùn)行 | 老年代 | 標(biāo)記-清除算法 |
G1 | 技術(shù)前沿成果序厉;空間整合锐膜;可預(yù)測(cè)停頓,消耗時(shí)間短 | 沒(méi)有限制 | 標(biāo)記-整理算法 |
參考
http://www.reibang.com/p/667e0d876666
http://www.reibang.com/p/3d9484f266ae