JAVA 垃圾收集算法,垃圾收集器與內存分配策略(內容全面,解析簡單易懂)

垃圾收集器須要解決的三個問題:java

1)哪些內存須要回收算法

2)何時回收數組

3)如何回收多線程

背景:程序計數器,虛擬機棧,本地方法棧3個區域隨線程而生,隨線程而滅,在這幾個區域內不須要過多的考慮回收的問題,由於方法結束或者線程結束時,內存天然就跟着回收了,因此咱們着重須要探究的是堆和方法區,由於他們是線程共享的,而且一個接口的多個實現類須要的內存可能不同,一個方法中的多個分支須要的內存也可能不同,只有在程序運行期間纔會知道建立哪些對象,這部份內存的分配和回收都是動態的,垃圾收集器所關注的內存就是這塊內存併發

 

問題1:怎麼判斷哪些內存是須要回收的?(不能再被任何途徑使用的對象就是須要回收的)佈局

方案1:引用計數法性能

給對象添加一個引用計數器,每當有一個地方引用他時,計數值就加1,當引用失效時,計數值就減一,任什麼時候候計數器爲0就是不可能再被使用,該對象的內存須要回收spa

缺點:不能解決循環引用的問題!因此沒有被當前虛擬機採用線程

方案2:可達性分析算法對象

經過一系列稱爲GC Roots的對象做爲起始點,從這些結點開始向下搜索,搜索走過的路徑叫作引用鏈,當GC Roots到這個對象不可達,則證實此對象不可用,該對象的內存須要回收

可做爲GC Roots的對象:

1)虛擬機棧中引用的對象

2)方法區中類靜態屬性引用的對象

3)方法區中常量引用的對象

4)本地方法棧中的JNI引用的對象(即Native方法)

 

引用分類

分類的目的:描述這樣一類對象:當內存空間足夠時,則能夠保留在內存中,若是內存空間在進行垃圾收集後仍是很是緊張,則能夠拋棄這些對象

具體分類:強引用,軟引用,弱引用,虛引用

強引用:相似Object obj=new Object()這類的引用,只要強引用還存在,則GC永遠不會回收掉被引用的對象

軟引用:描述一些還有用可是非必需的對象,在系統將要發生OOM異常以前,GC將會把這些對象進行第二次回收,若是此次回收以後仍是沒有足夠的內存,纔會拋出OOM異常,JDK提供了SofeRefrence類來實現軟引用

弱引用:描述非必需對象對象,當垃圾收集器工做時,不管當前內存是否足夠,都會回收掉只被弱引用關聯的對象,JDK提供了WeakRefrence類來實現弱引用

虛引用:爲對象設置虛引用的惟一目的就是在這個對象被收集器回收時收到一個系統通知

 

問題2:對象和GC Roots不可達時,對象有沒有自救的辦法?

要宣告一個對象死亡,至少要經歷兩次標記過程:

若是對象在進行可達性分析以後發現和GC Roots不可達,那麼它將會被第一次標記,而且進行第一次篩選,篩選的條件是對象是否有必要執行finalizefan方法

當對象沒有覆蓋finalize方法或者finalize方法已經被調用過,虛擬機都不會再執行finalize方法,若是虛擬機執行了對象的finalize方法,那麼對象將被放置在一個F-Queue的隊列中,並在稍後由一個虛擬機自動創建,低優先級的的Finalize線程去執行它(這裏的執行指的是虛擬機會觸發這個方法,但並不會承諾等待它運行結束,由於若是一個對象的finalize在隊列中執行緩慢,或者發生了死循環,將頗有可能致使隊列中其餘對象處於永久等待,甚至致使整個內存回收系統崩潰)

finalize方法是對象逃脫死亡命運最後一次機會,稍後GC將對隊列中的對象進行第二次標記,若是對象要在finalize成功拯救本身,只須要從新與引用鏈上的一個對象創建關聯便可,那麼他在第二次標記時將會被移除出回收集合,不然的話將會被回收

(任何一個對象的finalize方法都只會被系統執行一次!)

 

方法區的垃圾回收

方法區存儲的是類的信息和常量等,GC在方法區中回收的就是廢棄的常量和無用的類

判斷廢棄常量的:該常量沒有任何一個對象引用它

判斷無用的類:

1)該類的全部實例都被回收了

2)該類的類加載器已經被回收了

3)該類對應的java.lang.Class對象沒有在任何地方被引用,沒法在任何地方經過反射訪問該類1

 

垃圾收集算法

1.標記清除算法

首先標記出全部須要清除(回收)的對象,而後清除他們

QQ圖片20190329091546

缺點:1)效率問題,標記和清除兩個過程的效率都不高

     2)空間問題:標記清除以後會產生大量不連續的內存碎片

2.複製算法

將內存劃爲大小相等的兩塊,每次只使用其中的一塊,當這一塊的內存用完了,就將還存活的對象複製到另一塊上面,而後再把已使用過的內存空間一次清理掉,這樣使得每次都是對整個半區進行內存回收

QQ圖片20190329091604

缺點:1)代價太大,內存縮小爲原來的一半了

     2)效率問題;當對象存活率很高的時候,要進行不少的複製操做,效率低

該算法的應用:

商業虛擬機都是採用複製算法來回收新生代,不過劃分的兩塊內存比例不是1:1,而是8:1(Eden:Survivor),由於新生代中的對象百分之98都是「朝生夕死」,當新生代內存空間不夠時,咱們採用內存分配擔保機制:若是新生代上的空間不足存放上一次新生代收集存活下來的對象,這些對象將直接經過分配擔保機制進入老年代

3.標記整理算法

首先標記常須要清除的對象,而後將不須要清除的存活的對象向一邊移動,而後直接清理掉端邊界覺得的內存

QQ圖片20190329091529

4.分代收集算法

根據對象存活週期的不一樣將內存劃分爲不一樣的內存區,通常是劃分爲新生代和老年代,這樣就能夠根據不一樣代的特定採用不一樣代的垃圾收集算法,在新生代中,每次垃圾收集都有大量的對象死去,只有少許存活,那麼建議複製算法,只須要付出少許對象的複製成本就能夠完成,而老年代中由於對象的存活率高,沒有額外的空間進行分配擔保,就必須使用標記清理或者標記整理

 

垃圾收集器

1.Serial收集器(新生代收集器)

單線程收集器在它進行垃圾收集時會暫停其餘全部正在工做的線程,直到收集結束,這樣會致使Stop The World時間太長,帶來很差的體驗,可是它簡單高效,由於線程交互的開銷,能夠得到最高的單線程收集效率,在用戶桌面應用場景中,分配給虛擬機管理的內存通常不會很大,收集幾十兆甚至一兩百兆的新生代停頓時間能夠控制在幾十毫秒或者一百毫秒之內,只要不頻繁發生,仍是能夠接受的,因此Serial對運行在Client模式下的虛擬機來講是個很好的選擇

Serial新生代採用複製算法

 

2.ParNew收集器(新生代收集器)

Serial收集器的多線程版本,除Serial收集器外,ParNew是惟一能與CMS收集器配合工做的收集器

PerNew新生代採用複製算法

 

3.Parallel Scavenge收集器(新生代收集器,吞吐量優先的收集器)

新生代收集器,採用複製算法,並行的多線程收集器

和ParNew的差異在於它的關注點不一樣,CMS等收集器關注的是儘量的縮短用戶線程停頓時間,而Parallel Scavenge收集器關注的是一個可控制的吞吐量

須要知道的是,GC停頓時間的縮短是以犧牲吞吐量和新生代空間來換取的

GC自適應調節策略:虛擬機根據當前系統的運行狀況收集性能監控信息,動態調整參數以得到最小停頓時間或者最大吞吐量

 

4.Serial Old收集器(老年代收集器)

Serial收集器的老年代版本,單線程收集器,採用標記整理算法

給Client模式下的虛擬機使用,能夠在JDK1.5以及以前的版本中和Parallel Scavevge搭配使用,或者做爲CMS收集器的後備預案

 

5.Parallel Old收集器(老年代收集器)

Parallel Scavenge收集器的老年代版本,多線程,標記整理算法

和Parallel Scavenge搭配使用,這樣能夠構成完整的吞吐量優先或CPU資源敏感的收集器

 

6.CMS收集器(以最短停頓時間爲目的)(老年代收集器)

經典收集器,以獲取最小的停頓時間爲目標,經常使用於B/S系統的服務端

標記清除算法,不過過程比普通的標記清除更爲複雜,能夠分爲四個步驟

1)初始標記(須要Stop The World):標記GC Roots能直接關聯的對象

2)併發標記:進行GC Roots引用鏈搜索的過程標記

3)從新標記(須要 Stop The World):修正併發標記期間因用戶程序繼續運做而致使標記產生變更的那一部分的標記記錄

4)併發清除:併發的清除標記

CMS收集器的優勢:

1)併發收集!!!

2)低停頓

CMS收集器的缺點:

1)對CPU資源敏感

2)沒法處理浮動垃圾:CMS收集器和用戶程序並行執行,在CMS收集器工做期間還會產生新的垃圾,叫作浮動垃圾,因此CMS收集器不會像其餘收集器同樣等到老年代幾乎滿了再進行收集,須要預留一部分空間提供併發的用戶程序使用,默認老年代使用了百分之68就開始進行垃圾收集

3)內存碎片問題:能夠經過參數設置進行內存整理,不過內存整理過程沒法併發,碎片雖然沒有了,可是停頓時間變長了

 

7.G1收集器(Garbage-First)(面向服務端,收集器前沿成果之一)(整個堆的收集器)

特色:

1)並行與併發:G1能充分利用多核環境下的硬件優點,使用多核來縮短用戶線程停頓時間,部分其餘收集器本來須要停下java線程去執行GC動做,G1收集器仍然能夠經過併發的方式讓java程序和GC動做一塊兒繼續執行

2)分代收集

3)空間整合:G1從總體來看是屬於標記整理算法實現的收集器,可是從局部來看又是屬於複製算法實現的收集器,不管怎麼樣,這兩種算法都不會產生內存碎片,這樣有利於程序長時間運行

4)可預測的停頓:能創建可預測的停頓時間模型,能讓使用者明確指定在一個長度爲M毫秒的時間片內,消耗在垃圾收集上的時間不得超過N毫秒,這幾乎是實時垃圾收集器的特徵了

 

使用G1收集器時,java堆的內存佈局和其餘收集器有很大的差異,它將整個java堆多個大小相等的獨立區域,雖然還有新生代和老年代的概念,可是新生代和老年代已經不是物理隔離的了,他們都是一部分Region(不須要連續)的集合

 

問題:G1收集器爲何可以創建能夠預測的停頓時間模型?

由於G1收集器的堆的內存劃分是一個個大小相等的塊,它能夠避免在java堆中進行全局的垃圾收集,G1收集各個Region裏面垃圾的價值大小(回收所得到的空間大小和所需時間的經驗值),在後臺維護一個優先列表,每次根據容許的收集時間,優先回收價值大的Region

 

G1收集器的運做過程:

1)初始標記(須要Stop The World):標記GC Roots可以直接關聯的對象,修改一些TAMS(Next Top at Mark Start)的值,讓下一階段的用戶程序併發運行時,能在正確可用的Region中建立對象

2)併發標記:從GC Roots開始對堆中對象進行可達性分析,找出存活對象

3)最終標記:(須要Stop The World):修改在併發標記期間因用戶程序繼續運行而致使標記產生變化的那一部分標記

4)篩選回收:對各個Region的回收價值和成本進行排序,根據用戶指望的停頓時間來制定回收計劃

 

Full GC:在老年代中的GC動做

 

 

標記清除算法:CMS收集器,G1收集器

複製算法:Serial收集器,ParNew收集器,Parallel Scavenge收集器

標記整理算法:Serial Old收集器,Parallen Old收集器

 

新生代收集器:Serial收集器,ParNew收集器,Parallen Scavenen收集器

老年代收集器:Serial Old收集器,Parallen Old收集器,CMS收集器

整個堆的收集器:G1

 

垃圾收集器部分到此結束,咱們探討了這麼久的內存回收,咱們如今來看看內存分配的幾條規則:

1)對象優先的Eden(新生代)中分配,若是當前Eden新生代沒有足夠的空間進行分配,那麼將發起一次新生代的GC動做

2)大對象直接進入老年代(很長的字符串以及數組)

3)長期存活的對象直接進入老年代:每一個對象有年齡計數器,每活過一次新生代GC,年齡計數器+1,當年齡到達必定閥值,對象直接進入老年代

4)動態對象年齡斷定:虛擬機並非永遠的要求對象的年齡必須達到必定的閥值才能晉升到老年代,若是在Survivor老年代中相同年齡的全部對象的大小的和大於Survivor空間的一半,年齡大於等於該年齡的對象就能夠直接進入到老年代

5)空間分配擔保機制:若是新生代上的空間不足存放上一次新生代收集存活下來的對象,這些對象將直接經過分配擔保機制進入老年代

 

參考書籍:深刻理解JAVA虛擬機

相關文章
相關標籤/搜索