JVM——垃圾回收機制
文章目錄
判斷物件是否存活
引用計數法和可達性分析都是判斷物件是否存活的演算法
引用計數法
給物件新增一個引用計數器,每當有一個地方引用它時,計數器就加1,引用失效時計數器就減1,當引用計數器為0時,這個物件就是不可能再被使用的。
缺點
很難解決物件之間互相引用的問題
兩個物件obj1和obj2都有一個成員為instance
obj1.instance = obj2
obj2.instance = obj1
此時這兩個物件其實都無法被引用了,但是因為他們互相引用,導致引用計數器不為0,所以引用計數演算法無法通知GC 收集器去回收他們。
可達性分析
通過一系列的稱為“GC ROOT”的物件作為起始點,從這些節點向下搜尋,搜尋走過的路徑稱為“引用鏈”,當有個物件到“GC ROOT ”沒有一條引用鏈相連時,這個物件就是不可用的。
可作為GC Root的根節點有哪些?
1.虛擬機器棧中的引用的物件
2.方法中靜態屬性引用的物件
3.本地方法棧中JNI(也就是Native方法) 引用的物件
4.方法區中常量引用的物件
垃圾收集演算法
標記—清除
標記清除演算法分兩個階段
標記階段:對堆記憶體的物件進行可達性分析,將有引用的物件標記一下
清除階段:判斷堆記憶體的物件是否被標記,將未標記的物件回收
缺點:效率問題與空間問題
效率:標記和清除這兩個過程效率都不高。
不管標記、還是清除階段對我們整個堆記憶體進行遍歷,由於堆記憶體在JVM記憶體模型佔的空間很大,效率低下。
空間:在清除階段會產生很多不連續的記憶體碎片,新物件申請記憶體會存在記憶體不足,會引起再次 GC 問題。
複製演算法
為了解決標記-清除中的空間問題(記憶體碎片)、效率問題(遍歷整個堆空間)
將堆的空間分為兩部分,將物件放在其中一部分處理。
將有引用的物件複製到另一空間,對當前空間全部清除。
缺點
當物件存活率太高,複製存活物件去另一部分效率太低。
堆空間使用率太低,另外一半空間沒有用
標記—複製演算法
這是堆的空間模型(永久代不是所有的jvm都有),
IBM研究表明,年輕代的物件98%都是朝生夕死的,所以沒有必要將記憶體劃分為1:1的兩塊,而是將年輕代的記憶體分為三塊,Eden、From survivor、to survivor,每次使用Eden和From Survivor,每次使用Eden和From Survivor區,回收時,將Ende 和 From Surivor區存活的物件依次性的拷貝到to survivor的裡。
標記—整理演算法
標記階段和標記—清除演算法一樣
清除階段:讓所有存活的物件都向一端移動,然後直接清理掉端邊界以外的記憶體。
分代收集演算法
分代收集演算法就是對前面幾種演算法的整合,根據各個年代的特點進行最適當的收集演算法
年輕代:物件朝生夕死,每次只有少量物件存活,採用標記-複製演算法
老年代:物件存活率高,標記—清除、標記-整理
記憶體分配與回收策略
物件優先在Eden分配
大多數情況下,物件優先在Eden區分配,當Eden區沒有足夠的空間區分配的時候,虛擬機器將發起一次Minor GC
年輕代 GC(Minor GC):指發生在新生代的垃圾收集動作,因為java 中的物件大多數朝生夕死,所以Minor非常頻繁,一般來說回收速度也快.
老年代 GC (Full GC):發生在老年代的GC,出現了 Full GC經常會盤隨著至少一次的Minor GC,Full GC 的速度一般會比Minor GC 慢十倍以上
大物件直接進入老年堆
大物件:需要大量連續記憶體空間的java 物件
目的:為了避免新生代發生大量的記憶體拷貝
應當避免使用朝生夕死的大物件
長期存活物件進入老年堆
虛擬機器為每個物件定義了一個物件年齡計數器,物件在Eden區出生並經過一次GC 之後仍然存活,並且能夠被survivor容納的話,將被移動到survivor空間中去,並將物件的年齡設為1,物件在survivor中每經過一次GC ,年齡就增加一歲,當它的年齡增加到一定程度(預設15歲),就會被晉升到老年代中。
動態年齡分配策略
如果在survivor中相同年齡的物件的總和大於survivor空間的一半,年齡大於等於該年齡的物件就可直接進入老年代
空間分配擔保
在發生minor gc之前,虛擬機器會檢測 : 老年代最大可用的連續空間>新生代all物件總空間
1、滿足,minor gc是安全的,可以進行minor gc。
2、不滿足,虛擬機器檢視HandlePromotionFailure引數:
(1)為true,允許擔保失敗,會繼續檢測老年代最大可用的連續空間>歷次晉升到老年代物件的平均大小。若大於,將嘗試進行一次minor gc,若失敗,則重新進行一次full gc。
(2)為false,則不允許冒險,要進行full gc(對老年代進行gc)。
新生代使用複製收集演算法,但為了記憶體利用率,只使用其中一個Survivor空間來作為輪換備份,因此當出現大量物件在Minor GC後仍然存活的情況(最極端的情況就是記憶體回收後新生代中所有物件都存活),就需要老年代進行分配擔保,把Survivor無法容納的物件直接進入老年代。老年代要進行這樣的擔保,前提是老年代本身還有容納這些物件的剩餘空間,一共有多少物件會活下來在實際完成記憶體回收之前是無法明確知道的,所以只好取之前每一次回收晉升到老年代物件容量的平均大小值作為經驗值,與老年代的剩餘空間進行比較,決定是否進行Full GC來讓老年代騰出更多空間。