JAVA垃圾回收
時(shí)間:20180307
問(wèn)題:
-
如何判定對(duì)象為垃圾對(duì)象
- 引用計(jì)數(shù)法
- 可達(dá)性分析法
-
如何回收
- 回收策略
- 標(biāo)記-清除
- 復(fù)制算法
- 標(biāo)記-整理算法
- 分代收集算法
- 垃圾回收器
- Serial
- Parnew
- Cms
- G1
- 回收策略
-
何時(shí)回收
引用計(jì)數(shù)法
??很多教科書(shū)給出如下定義:給對(duì)象添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí)屋确,計(jì)數(shù)器值加1曹宴;當(dāng)引用失效時(shí),計(jì)數(shù)器值就減1佑附;任何時(shí)刻計(jì)數(shù)器為0的對(duì)象就是不可能再被使用的。
??但是,至少主流的JAVA虛擬機(jī)里面沒(méi)有選用引用計(jì)數(shù)法來(lái)管理內(nèi)存观游,其中最主要的原因就是它很難解決對(duì)象之間的循環(huán)引用的問(wèn)題永票。
打印詳細(xì)的GC日志信息參數(shù)(VM arguments):
-verbose:gc
-XX:+PrintGCDetails
測(cè)試堆中多個(gè)對(duì)象之間存在循環(huán)引用卵贱,外部沒(méi)有引用,垃圾回收情況:
public class Main {
public Object instance = null;
public static void main(String[] args) {
Main m1 = new Main();
Main m2 = new Main();
m1.instance = m2;
m2.instance = m1;
m1 = null;
m2 = null;
System.gc();
}
}
結(jié)果:
[GC (System.gc()) [PSYoungGen: 1996K->936K(38400K)] 1996K->944K(125952K), 0.0012261 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 936K->0K(38400K)] [ParOldGen: 8K->565K(87552K)] 944K->565K(125952K), [Metaspace: 2735K->2735K(1056768K)], 0.0054020 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 38400K, used 333K [0x00000000d5900000, 0x00000000d8380000, 0x0000000100000000)
eden space 33280K, 1% used [0x00000000d5900000,0x00000000d59534a8,0x00000000d7980000)
from space 5120K, 0% used [0x00000000d7980000,0x00000000d7980000,0x00000000d7e80000)to space 5120K, 0% used [0x00000000d7e80000,0x00000000d7e80000,0x00000000d8380000)
ParOldGen total 87552K, used 565K [0x0000000080a00000, 0x0000000085f80000, 0x00000000d5900000)
object space 87552K, 0% used [0x0000000080a00000,0x0000000080a8d770,0x0000000085f80000)
Metaspace used 2741K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 304K, capacity 386K, committed 512K, reserved 1048576K
public class Main {
public Object instance = null;
public Main() {
byte [] m = new byte[20 * 1024 * 1024];
}
public static void main(String[] args) {
Main m1 = new Main();
Main m2 = new Main();
m1.instance = m2;
m2.instance = m1;
m1 = null;
m2 = null;
System.gc();
}
}
結(jié)果:
[GC (System.gc()) [PSYoungGen: 22476K->872K(38400K)] 42956K->21360K(125952K), 0.0010626 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 872K->0K(38400K)] [ParOldGen: 20488K->565K(87552K)] 21360K->565K(125952K), [Metaspace: 2735K->2735K(1056768K)], 0.0052890 secs] [Times: user=0.14 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 38400K, used 333K [0x00000000d5900000, 0x00000000d8380000, 0x0000000100000000)
eden space 33280K, 1% used [0x00000000d5900000,0x00000000d59534a8,0x00000000d7980000)
from space 5120K, 0% used [0x00000000d7980000,0x00000000d7980000,0x00000000d7e80000)
to space 5120K, 0% used [0x00000000d7e80000,0x00000000d7e80000,0x00000000d8380000)
ParOldGen total 87552K, used 565K [0x0000000080a00000, 0x0000000085f80000, 0x00000000d5900000)
object space 87552K, 0% used [0x0000000080a00000,0x0000000080a8d770,0x0000000085f80000)
Metaspace used 2741K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 304K, capacity 386K, committed 512K, reserved 1048576K
圖解:
可達(dá)性分析算法
這個(gè)算法的基本思路就是通過(guò)一系列的稱(chēng)為“GC Roots”的對(duì)象作為起點(diǎn)侣集,從這個(gè)節(jié)點(diǎn)開(kāi)始向下搜索键俱,搜索走過(guò)的路徑稱(chēng)為引用鏈,當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈時(shí)世分,則證明此對(duì)象是不可用的编振。就是從GCRoots到這個(gè)對(duì)象是不可達(dá)的。
可作為G'C'Roots的對(duì)象
- 虛擬機(jī)棧(局部變量表)
- 方法區(qū)的類(lèi)靜態(tài)屬性所引用的對(duì)象
- 方法區(qū)中常量所引用的對(duì)象
- 本地方法棧中引用的對(duì)象
垃圾收集算法
標(biāo)記-清楚算法
最基礎(chǔ)的收集算法是“標(biāo)記-清除(Mark-Sweep)”算法臭埋,如同它的名字一樣踪央,算法分為“標(biāo)記”和"清除"兩個(gè)階段:首先標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記后統(tǒng)一回收所有被標(biāo)記的對(duì)象瓢阴。標(biāo)記過(guò)程就是(可達(dá)性分析算法)畅蹂。
不足之處
1.效率問(wèn)題:標(biāo)記和清除兩個(gè)過(guò)程的效率都不高。
2.空間問(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)作。
復(fù)制算法
復(fù)制算法:它將可用內(nèi)存按容量劃分為大小相等的涼快募胃,每次只使用其中的一塊旗唁,當(dāng)這一塊的內(nèi)存用完了,就將還存活的對(duì)象復(fù)制到另一塊上面痹束,然后再把已使用過(guò)的內(nèi)存空間一次清理掉检疫。
堆
1.新生代
- Eden 伊甸園
- Survivor 存活區(qū)
- Tenured Gen
從
2.老年代
標(biāo)記 -整理算法
標(biāo)記-整理算法有可以叫做標(biāo)記-整理-清除算法。其中標(biāo)記過(guò)程仍然與“標(biāo)記-清除”算法一樣祷嘶,但后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行清理屎媳,而是讓所有存活的對(duì)象都向一端移動(dòng)夺溢,然后直接清理掉端邊界以外的內(nèi)存(被標(biāo)記需要清除的對(duì)象)。
復(fù)制收集算法在對(duì)象存活率較高時(shí)就要進(jìn)行較多的復(fù)制操作烛谊,效率將會(huì)變低风响。更關(guān)鍵的是,如果不想浪費(fèi)50%的空間丹禀,就需要有額外的空間進(jìn)行分配擔(dān)保状勤,已應(yīng)對(duì)被使用的內(nèi)存中對(duì)象都100%存活的極端情況,所以在老年代一般不能直接用這種算法双泪。
分代收集算法
??當(dāng)前商業(yè)虛擬機(jī)的垃圾收集都采用“分代收集Generational Collection算法”持搜,只是根據(jù)對(duì)象存活的周期的不同將內(nèi)存劃分為幾塊。一般把JAVA堆分為新生代和老年代焙矛。這樣就可以根據(jù)各個(gè)年代的特點(diǎn)采用最適合的收集算法葫盼。在新生代中,每次垃圾收集時(shí)都發(fā)現(xiàn)有大批對(duì)象死去村斟,只有少量存活贫导,那就使用復(fù)制算法,只需要付出少量存活對(duì)象的復(fù)制成本就可以完成收集蟆盹。而老年代中因?yàn)閷?duì)象存活率高脱盲、沒(méi)有額外的空間對(duì)它進(jìn)行分配擔(dān)保,就必須使用“標(biāo)記-清理”或者“標(biāo)記-整理”算法來(lái)進(jìn)行回收日缨。