java的GC機制主要針對堆區中的對象。本文從兩個方面描述JavaGC機制
1. 如何判定對象應該被回收
2. GC算法
一、如何判斷哪些對象應該被回收
1.判定對象是否被回收的兩種方法
(1)引用計數法
有引用加1,放棄引用減1,當為0時表示可以被回收。
實現簡單效率高,但是無法解決對象循環引用的問題
(2)可達性分析
將一系列的稱作GCRoot的對象作為跟節點,某個對象到這些GCRoot之間有鏈路可達,則該對象不應該是被回收的。
哪些可作為GCRoot?
1. 虛擬機棧棧幀的局部變量表中引用的對象
2. 方法區中類的靜態屬性引用的對象
3.方法區中常亮引用的對象
4. 本地方法棧中Native方法引用的對象
2. 四種引用方式和區別
(1)強引用
只要強引用存在就不會回收掉被引用的對象,虛擬機寧可拋出OOM
(2)軟引用
虛擬機在將要發生OOM之前進行一次針對該類對象的回收,如果此次回收內存夠用則好,不夠用則會拋出OOM。
(3)弱引用
虛擬機每次發起GC時都會針對該類對象進行回收。無論內存是否緊張。
(4)虛引用
該類引用其實與對象的生命周期并無關系,也無法通過該引用獲得該類對象的實例。唯一用處是可以設置一個虛引用關聯,當該類對象被回收時可以獲得系統通知。
3. 回收過程及對象的自我救贖
一個對象真正死亡,至少要經歷兩次標記過程。
第一次標記過程中如果發現對象沒有雨GCRoot相連,則被標記一次,并查看該對象是否覆蓋了finalize()方法,如果覆蓋了finalize方法則將該對象加入一個F-Queue的隊列中。稍后由虛擬機的Finalizer線程來執行它的方法。但是該線程只是保證該方法有執行機會,并不一定等到它執行完,因為防止里面有死循環導致虛擬機崩潰。
對象可以在finalize方法中將自己與GCRoot相連以拯救自己。
對象的finalize方法只會執行一次。
4. 回收方法區
方法區的回收主要是針對廢棄的常量和無用的類。
廢棄的常量:該常量沒有被任何變量引用
無用的類:
(1)該類的所有實例都已經被回收
(2)該類的classLoder已經被回收
(3)該類對應的java.lang.class對象沒有在任何地方被引用,無法通過反射訪問該類的任何方法。
二、垃圾搜集算法
1. 標記-清除算法
(1)標記:掃描之后對被認定為需要回收的對象進行回收
(2)清除:對被標記的對象進行GC
- 缺點:
- 標記和清除兩個過程效率都不高
- 容易造成內存碎片,不利于后續大對象的內存分配
2.復制算法
(1)將內存分為兩塊,每次只使用其中的一塊。
(2)當其中一塊要用完時,標記對象將要存活的對象復制到另外一塊上去,然后針對當前塊進行整塊回收。
- 優點:實現簡單高效,無內存碎片。
- 缺點:可用內存變成原來的一半。
3. 標記整理算法
(1)先對將要被回收的對象進行標記
(2)將要存活的對象向內存的一邊移動整理
(3)將可存活對象端邊界以外的對象進行回收
4. 分代收集算法
將堆劃分為新生代和老年代
(1)老年代:采用標記清除或標記整理算法
(2)新生代:采用復制算法
新生代分為Eden區和兩個survival區,每次使用Eden區和一個survival區,當要回收時將要存活的對象復制survival To區。
實驗表明,新生代的對象朝生熄滅,一次回收率可達約98%。所以需要復制的對象很少。(注:老年代對新生代的擔保機制)
三、內存分配規則
1. 對象優先分配在Eden區
- 新生代GC:(Minor GC)頻繁且速度快
- 老年代GC:(Major GC)速度比Minor GC慢10倍以上
2. 大對象直接進入老年代:如數組
3. 長期存活的對象進入老年代
新生代的對象每躲過一次GC則增加一歲,當超過默認15歲的時候就認為是長期存活的對象,將進入老年代。
4. 動態對象年齡判定
當新生代中對象沒有達到15歲年齡,但是survival區存活的對象中某個相同年齡的所有對象占用的空間超過survival區內存的一半時,將這個年齡及大于這個年齡的對象移至老年代。
5. 空間分配擔保
Minor GC之前虛擬機會檢查老年代的剩余空間是否足夠容納新生代存活對象的總空間,如果大于,則安全,否則在允許擔保失敗的情況下,檢查老年代最大可用連續空間是否大于歷次新生代晉升到老年代的空間大小,如果大于則進行一次Minor GC(有風險,擔保失敗的話會進行一次Full GC),如果小于則進行一次Full GC。