Q:Jvm內(nèi)存模型?
A:Jvm(Java虛擬機(jī))主要管理兩種類型內(nèi)存:堆和非堆。 堆是運(yùn)行時(shí)數(shù)據(jù)區(qū)域,所有類實(shí)例和數(shù)組的內(nèi)存均從此處分配寂祥。 非堆是JVM留給自己用的,包含方法區(qū)七兜、JVM內(nèi)部處理或優(yōu)化所需的內(nèi)存(如 JIT Compiler,Just-in-time Compiler福扬,即時(shí)編譯后的代碼緩存)腕铸、每個(gè)類結(jié)構(gòu)(如運(yùn)行時(shí)常數(shù)池惜犀、字段和方法數(shù)據(jù))以及方法和構(gòu)造方法的代碼。
簡(jiǎn)言之狠裹,Java程序內(nèi)存主要(這里強(qiáng)調(diào)主要二字)分兩部分虽界,堆和非堆。大家一般new的對(duì)象和數(shù)組都是在堆中的涛菠,而GC主要回收的內(nèi)存也是這塊堆內(nèi)存莉御。
Java虛擬機(jī)采用的是分代回收算法
幾種垃圾回收算法
1:標(biāo)記清除算法 (Mark-Sweep)
標(biāo)記-清除算法分為兩個(gè)階段:標(biāo)記階段和清除階段。標(biāo)記階段的任務(wù)是標(biāo)記出所有需要被回收的對(duì)象俗冻,清除階段就是回收被標(biāo)記的對(duì)象所占用的空間礁叔。 優(yōu)點(diǎn)是簡(jiǎn)單,容易實(shí)現(xiàn)迄薄。 缺點(diǎn)是容易產(chǎn)生內(nèi)存碎片琅关,碎片太多可能會(huì)導(dǎo)致后續(xù)過程中需要為大對(duì)象分配空間時(shí)無(wú)法找到足夠的空間而提前觸發(fā)新的一次垃圾收集動(dòng)作
2:復(fù)制算法 (Copying)
復(fù)制算法將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊讥蔽。當(dāng)這一塊的內(nèi)存用完了涣易,就將還存活著的對(duì)象復(fù)制到另外一塊上面,然后再把已使用的內(nèi)存空間一次清理掉冶伞,這樣一來(lái)就不容易出現(xiàn)內(nèi)存碎片的問題新症。 優(yōu)缺點(diǎn)就是,實(shí)現(xiàn)簡(jiǎn)單响禽,運(yùn)行高效且不容易產(chǎn)生內(nèi)存碎片徒爹,但是卻對(duì)內(nèi)存空間的使用做出了高昂的代價(jià),因?yàn)槟軌蚴褂玫膬?nèi)存縮減到原來(lái)的一半金抡。 從算法原理我們可以看出瀑焦,Copying算法的效率跟存活對(duì)象的數(shù)目多少有很大的關(guān)系,如果存活對(duì)象很多梗肝,那么Copying算法的效率將會(huì)大大降低
3:標(biāo)記整理算法 (Mark-Compact)
該算法標(biāo)記階段和Mark-Sweep一樣榛瓮,但是在完成標(biāo)記之后,它不是直接清理可回收對(duì)象巫击,而是將存活對(duì)象都向一端移動(dòng)禀晓,然后清理掉端邊界以外的內(nèi)存。 所以坝锰,特別適用于存活對(duì)象多粹懒,回收對(duì)象少的情況下
4:分代回收算法
分代回收算法其實(shí)不算一種新的算法,而是根據(jù)復(fù)制算法和標(biāo)記整理算法的的特點(diǎn)綜合而成. 復(fù)制算法:適用于存活對(duì)象很少顷级≠旃裕回收對(duì)象多 標(biāo)記整理算法: 適用用于存活對(duì)象多,回收對(duì)象少
備注:對(duì)于新生代采取Copying算法,因?yàn)樾律忻看卫厥斩家厥沾蟛糠謱?duì)象帽芽,也就是說(shuō)需要復(fù)制的操作次數(shù)較少删掀,采用Copying算法效率最高。但是导街,但是披泪,但是,實(shí)際中并不是按照上面算法中說(shuō)的1:1的比例來(lái)劃分新生代的空間的搬瑰,而是將新生代劃分為一塊較大的Eden空間和兩塊較小的Survivor空間款票,比例為8:1:1.。為什么泽论?下一節(jié)深入分析艾少。
由于老年代的特點(diǎn)是每次回收都只回收少量對(duì)象,一般使用的是Mark-Compact算法佩厚。
Q:為什么不是一塊Survivor空間而是兩塊姆钉?
A: 這里涉及到一個(gè)新生代和老年代的存活周期的問題,比如一個(gè)對(duì)象在新生代經(jīng)歷15次(僅供參考)GC抄瓦,就可以移到老年代了潮瓶。問題來(lái)了,當(dāng)我們第一次GC的時(shí)候钙姊,我們可以把Eden區(qū)的存活對(duì)象放到Survivor A空間毯辅,但是第二次GC的時(shí)候,Survivor A空間的存活對(duì)象也需要再次用Copying算法煞额,放到Survivor B空間上思恐,而把剛剛的Survivor A空間和Eden空間清除。第三次GC時(shí)膊毁,又把Survivor B空間的存活對(duì)象復(fù)制到Survivor A空間胀莹,如此反復(fù)。 所以婚温,這里就需要兩塊Survivor空間來(lái)回倒騰描焰。
Q:為什么Eden空間這么大而Survivor空間要分的少一點(diǎn)?
A: 新創(chuàng)建的對(duì)象都是放在Eden空間栅螟,這是很頻繁的荆秦,尤其是大量的局部變量產(chǎn)生的臨時(shí)對(duì)象,這些對(duì)象絕大部分都應(yīng)該馬上被回收力图,能存活下來(lái)被轉(zhuǎn)移到survivor空間的往往不多步绸。所以,設(shè)置較大的Eden空間和較小的Survivor空間是合理的吃媒,大大提高了內(nèi)存的使用率瓤介,緩解了Copying算法的缺點(diǎn)吕喘。 我看8:1:1就挺好的,當(dāng)然這個(gè)比例是可以調(diào)整的刑桑,包括上面的新生代和老年代的1:2的比例也是可以調(diào)整的兽泄。 新的問題又來(lái)了,從Eden空間往Survivor空間轉(zhuǎn)移的時(shí)候Survivor空間不夠了怎么辦漾月?直接放到老年代去
Q: Eden空間和兩塊Survivor空間的工作流程?
// 分配了一個(gè)又一個(gè)對(duì)象
放到Eden區(qū)
// 不好,Eden區(qū)滿了胃珍,只能GC(新生代GC:Minor GC)了
把Eden區(qū)的存活對(duì)象copy到Survivor A區(qū)梁肿,然后清空Eden區(qū)(本來(lái)Survivor B區(qū)也需要清空的,不過本來(lái)就是空的)
// 又分配了一個(gè)又一個(gè)對(duì)象
放到Eden區(qū)
// 不好觅彰,Eden區(qū)又滿了吩蔑,只能GC(新生代GC:Minor GC)了
把Eden區(qū)和Survivor A區(qū)的存活對(duì)象copy到Survivor B區(qū),然后清空Eden區(qū)和Survivor A區(qū)
// 又分配了一個(gè)又一個(gè)對(duì)象
放到Eden區(qū)
// 不好填抬,Eden區(qū)又滿了烛芬,只能GC(新生代GC:Minor GC)了
把Eden區(qū)和Survivor B區(qū)的存活對(duì)象copy到Survivor A區(qū),然后清空Eden區(qū)和Survivor B區(qū)
// ...
// 有的對(duì)象來(lái)回在Survivor A區(qū)或者B區(qū)呆了比如15次飒责,就被分配到老年代Old區(qū)
// 有的對(duì)象太大赘娄,超過了Eden區(qū),直接被分配在Old區(qū)
// 有的存活對(duì)象宏蛉,放不下Survivor區(qū)遣臼,也被分配到Old區(qū)
// ...
// 在某次Minor GC的過程中突然發(fā)現(xiàn):
// 不好,老年代Old區(qū)也滿了拾并,這是一次大GC(老年代GC:Major GC)
Old區(qū)慢慢的整理一番揍堰,空間又夠了
// 繼續(xù)Minor GC
// ...
觸發(fā)GC的類型:
GC_FOR_MALLOC: 表示是在堆上分配對(duì)象時(shí)內(nèi)存不足觸發(fā)的GC。 GC_CONCURRENT: 當(dāng)我們應(yīng)用程序的堆內(nèi)存達(dá)到一定量嗅义,或者可以理解為快要滿的時(shí)候屏歹,系統(tǒng)會(huì)自動(dòng)觸發(fā)GC操作來(lái)釋放內(nèi)存。 GC_EXPLICIT: 表示是應(yīng)用程序調(diào)用System.gc之碗、VMRuntime.gc接口或者收到SIGUSR1信號(hào)時(shí)觸發(fā)的GC蝙眶。 GC_BEFORE_OOM: 表示是在準(zhǔn)備拋OOM異常之前進(jìn)行的最后努力而觸發(fā)的GC
備注:此篇文章只作為筆記記錄。
感謝 文章原著:https://github.com/xiangjiana/Android-MS/blob/master/study/framework/Android%E6%B6%88%E6%81%AF%E6%9C%BA%E5%88%B6.md