前言
為什么需要垃圾回收
- 首先我們來聊聊為什么會需要垃圾回收翘瓮,假設我們不進行垃圾回收會造成什么后果,我們舉一個簡單的例子
- 我們住在一個房子里面裤翩,我們每天都在里面生活资盅,然后垃圾都丟在房子里面,又不打掃踊赠,最后房子都是垃圾 我們是不是就沒法住下去了呵扛。
- 所以JVM垃圾回收機制也是一樣的,當我們創(chuàng)建的對象占據(jù)堆空間要滿了的的時候我們就對他進行垃圾回收筐带,注意java的垃圾回收是不定時的今穿,c語言的是需要去調(diào)用垃圾回收方法
- 剛剛也說到 上面舉的例子也說到 假設一個房子都被垃圾堆滿了 那么我們沒法住人了 那么我們是不是會告訴別人這個房子沒法住人了 而java也是如此當我們堆空間滿了的時候 此時它就會拋出異常OutOfMemoryError(簡稱OOM)
什么地方需要進行垃圾回收
剛剛我們說了為什么要回收垃圾,和什么是OOM那么我們下面就給大家介紹伦籍,我們JVM中什么地方需要進行垃圾回收蓝晒。
垃圾回收要考慮的點
1)是否會產(chǎn)生垃圾
2)哪些內(nèi)存需要回收
3)什么時候回收
4)如何對他進行回收
程序計數(shù)器
jvm中唯一 一個不需要垃圾回收的地方。
棧 本地方法棧
這個地方會因為棧幀存滿了導致內(nèi)存溢出帖鸦,所以需要垃圾回收
方法區(qū)(元空間)
這個地方也需要進行垃圾回收
堆
這個地方是我們垃圾回收最頻繁的地方芝薇,我們幾乎我們所有的對象都存儲在堆中,也是我們今天要著重講的地方
堆GC
堆作儿,可能大家都不陌生洛二,可是好像又距離我們很遠,今天它來了
從上面的圖我們可以看出 攻锰,我們的堆空間被主要被劃分為了二塊區(qū)域 灭红,新生代 ,老年代 java堆是我們JVM中管理區(qū)域最大的一塊口注, java堆是一個線程共享的區(qū)域 变擒,在虛擬機啟動時創(chuàng)建 ,幾乎所有的對象都在此分配寝志, java虛擬機規(guī)范中有過描述娇斑,所有的實例對象以及數(shù)組都在堆中進行分配內(nèi)存, 但目前因為JTI編譯器的發(fā)展材部,和逃逸分析技術的逐漸完善毫缆,在堆中分配對象也不是那么的絕對了。
堆的內(nèi)存在物理上可以是不連續(xù)的乐导,但是在邏輯上是即可苦丁。
新生代
- 我們對象的創(chuàng)建到結束,幾乎是"朝生夕死"的一個過程差不多90%的對象都在新生代被回收了物臂,所以新生代的gc也是發(fā)生最為頻繁的一個區(qū)域旺拉。新生代產(chǎn)生gc我們稱為Y-GC
- 每產(chǎn)生一次y-gc我們對象的年齡就加一歲产上,直到15歲后進入老年代。當然這是正常情況蛾狗,那么有沒有特殊情況勒當然有
空間擔保
- 當我們創(chuàng)建的對象大于Eden的時候晋涣,此時怎么辦,此時他會先產(chǎn)生一次Y-GC如果還是無法存儲下新創(chuàng)建的對象沉桌,那么我們就會通過空間擔保策略進入老年代谢鹊。
- 還有一種情況,對象創(chuàng)建也會直接進入老年代留凭,當我們的Surivivor區(qū)滿了的時候佃扼,此時它不會主動產(chǎn)生gc只會依賴于Eden,但我們的對象又不能被拋棄蔼夜,所以它也被分配到了老年代
- 當然我們實際開發(fā)工作中需要盡量的去避免這種情況的誕生
動態(tài)年齡
- 什么是動態(tài)年齡勒兼耀,這是堆中的另一個,擔保策略了挎扰,它會去判斷我們Surivivor的區(qū)中,相同年齡的對象大于Surivivor區(qū)一半的時候巢音,那么他就會判定此時這些對象已經(jīng)能夠很好的存活了遵倦,所以他們就集體被丟到老年代了
對象如何分配內(nèi)存
老年代
- 我們老年代存放的都是一些老對象了,大對象官撼,都是存活時間較長的對象這里一般很少產(chǎn)生FGC這里一旦產(chǎn)生FGC那么所產(chǎn)生GC的耗時將會是YGC的10倍時耗梧躺,而我們老年代快要存滿時進入了一個對象,這時會產(chǎn)生一次FGC如果GC結束后傲绣,還是無法存放對象的話此時就會報OOM異常掠哥。
垃圾回收算法
分代算法
分代算法,其實也就是將我們堆空間劃分為了一個個不同的區(qū)域秃诵,新生代续搀,老年代,不讓它回收的時候對整個堆進行一個回收菠净。減少GC所停頓的時間禁舷,我們稱之為STW (Stop The World),假設我們堆整個堆進行垃圾回收,是不是每次都需要去把整個堆的垃圾標記一次毅往,非常的那么用戶線程停止的時間就非常長牵咙,你想一下,假如你的電腦每使用1個小時就卡10秒攀唯,那么你是不是非常操蛋洁桌。
標記清除
算法執(zhí)行過程
堆空間垃圾清理前
垃圾清除后
算法介紹
標記清除是最開始jvm選擇的一種垃圾回收算法,這個算法就和他的名字一樣侯嘀,分為標記另凌,和清除二個過程谱轨,首先他會標記所有需要回收的對象,標記結束后對標記的對象進行一個垃圾回收途茫。
缺點
這種方式會有什么缺點呢碟嘴!它會導致內(nèi)存空間的浪費,產(chǎn)生大佬不連續(xù)的內(nèi)存碎片囊卜,當我們需要一個連續(xù)的內(nèi)存空間存放大對象的時候娜扇,因為連續(xù)的內(nèi)存空間不夠,導致我們不得又產(chǎn)生一次GC栅组,提高了我們GC產(chǎn)生的頻率雀瓢。
標記整理
算法執(zhí)行過程
算法介紹
標記整理,的執(zhí)行過程玉掸,于標記清除相反刃麸,標記清除是標記需要回收的對象,而標記整理卻是標記存活的對象司浪,然后把他們?nèi)肯蛞欢芜M行位移泊业,然后清除端邊界以外的所有對象。
適用范圍
老年代垃圾回收
復制算法
算法執(zhí)行過程
算法介紹
復制算法也是在標記清除上的一個改進啊易,它彌補了標記清除出現(xiàn)大量不連續(xù)內(nèi)存碎片的缺點吁伺。它將一個可用的內(nèi)存空間劃分為大小相等的二塊區(qū)域,每次只使用其中一塊區(qū)域租谈,當這塊區(qū)域用完了就把存活的對象放到篮奄,另一塊空著的區(qū)域區(qū),然后把自己清除干凈割去,變成一塊空著的區(qū)域窟却。這樣就解決了內(nèi)存碎片的問題
缺點
那么這樣的算法是不是太過于苛刻了,每次都需要一塊空著的區(qū)域用于存放對象呻逆,犧牲掉了大量的內(nèi)存夸赫。
適用范圍
新生代Surivivor區(qū)域
堆中對象內(nèi)存的分配策略
指針碰撞
這種分配方式其實是復制算法,標記整理中的攜帶的一種對象分配策略咖城,我們?nèi)绾螀^(qū)分什么是用過的憔足,什么是沒用過的,這時我們通過一個指針酒繁,作為一個分界點指示器滓彰,那所需要分配的內(nèi)存,就僅僅是把指示器指針向空閑空間那邊挪動一段與對象大小相等的距離州袒,這種分配方式稱為"指針碰撞"(Bump the Pointer)揭绑。
空閑列表
空閑是標記清除中對對象分配的一個策略,因為標記清除中我們的內(nèi)存劃分的隨機的,已使用內(nèi)存和未使用內(nèi)存相互交錯他匪,那么我們?nèi)绾伟阉麄冴P聯(lián)起來菇存,虛擬機針對這種交錯的內(nèi)存維護了一個列表,記錄哪些內(nèi)存塊是可用的邦蜜,在分配的時候找到一塊足夠大的空間劃分給對象實例依鸥,并更新列表上的記錄,這種分配方式成為"空閑列表"(Free List)悼沈。
OOM異常
其實OOM在上面介紹了堆內(nèi)存的劃分和收集過程中贱迟,大家也應該對它有了一定的認識了,OOM異常是發(fā)生在老年代Old中的一個異常絮供,當我們老年代中無法在存放對象的時候衣吠,就會報OOM內(nèi)存溢出異常
public class HeapOomError {
public static void main(String[] args) {
List<byte[]> list =new ArrayList<>();
int i=0;
while (true){
try {
Thread.sleep(100);
} catch (InterruptedException e){
e.printStackTrace();
}
list.add(new byte[5 * 1024 * 1024]);
//System.out.println("count is:"+(++i));
}
}
}
設置堆空間的大小
最后我們得到的結果如下
總結
總而言之我們需要的優(yōu)化的GC的損耗和避免內(nèi)存溢出的出現(xiàn),從而提高我用戶良好使用體驗壤靶。
最后
感謝你看到這里缚俏,看完有什么的不懂的可以在評論區(qū)問我,覺得文章對你有幫助的話記得給我點個贊贮乳,每天都會分享java相關技術文章或行業(yè)資訊忧换,歡迎大家關注和轉發(fā)文章!