JVM的判斷對象是否已死和四種垃圾回收算法總結

面試題一:判斷對象是否已死

判斷對象是否已死就是找出哪些對象是已經死掉的,之後不會再用到的,就像地上有廢紙、飲料瓶和百元大鈔,掃地前要先判斷出地上廢紙和飲料瓶是垃圾,百元大鈔不是垃圾。判斷對象是否已死有引用計數算法和可達性分析算法。java

1.引用計數算法面試

給每個對象添加一個引用計數器,每當有一個地方引用它時,計數器值加 1;每當有一個地方再也不引用它時,計數器值減 1,這樣只要計數器的值不爲 0,就說明還有地方引用它,它就不是無用的對象。以下圖,對象 2 有 1 個引用,它的引用計數器值爲 1,對象 1有兩個地方引用,它的引用計數器值爲 2 。算法

JVM的判斷對象是否已死和四種垃圾回收算法總結

這種方法看起來很是簡單,但目前許多主流的虛擬機都沒有選用這種算法來管理內存,緣由就是當某些對象之間互相引用時,沒法判斷出這些對象是否已死,以下圖,對象 1 和對象 2 都沒有被堆外的變量引用,而是被對方互相引用,這時他們雖然沒有用處了,可是引用計數器的值仍然是 1,沒法判斷他們是死對象,垃圾回收器也就沒法回收。數組

JVM的判斷對象是否已死和四種垃圾回收算法總結

2.可達性分析算法安全

瞭解可達性分析算法以前先了解一個概念——GC Roots,垃圾收集的起點,能夠做爲 GC Roots 的有虛擬機棧中本地變量表中引用的對象、方法區中靜態屬性引用的對象、方法區中常量引用的對象、本地方法棧中 JNI(Native 方法)引用的對象。ide

當一個對象到 GC Roots 沒有任何引用鏈相連(GC Roots 到這個對象不可達)時,就說明此對象是不可用的,是死對象。對象

以下圖:object一、object二、object三、object4 和 GC Roots 之間有可達路徑,這些對象不會被回收,但 object五、object六、object7 到 GC Roots 之間沒有可達路徑,這些對象就被判了死刑。內存

JVM的判斷對象是否已死和四種垃圾回收算法總結

上面被判了死刑的對象(object五、object六、object7)並非必死無疑,還有挽救的餘地。進行可達性分析後對象和 GC Roots 之間沒有引用鏈相連時,對象將會被進行一次標記,接着會判斷若是對象沒有覆蓋 Object的finalize() 方法或者 finalize() 方法已經被虛擬機調用過,那麼它們就會被行刑(清除);若是對象覆蓋了 finalize() 方法且尚未被調用,則會執行 finalize() 方法中的內容,因此在 finalize() 方法中若是從新與 GC Roots 引用鏈上的對象關聯就能夠拯救本身,可是通常不建議這麼作,周志明老師也建議你們徹底能夠忘掉這個方法~虛擬機

3.方法區回收it

上面說的都是對堆內存中對象的判斷,方法區中主要回收的是廢棄的常量和無用的類。

判斷常量是否廢棄能夠判斷是否有地方引用這個常量,若是沒有引用則爲廢棄的常量。

判斷類是否廢棄須要同時知足以下條件:

該類全部的實例已經被回收(堆中不存在任何該類的實例)。

加載該類的 ClassLoader 已經被回收。

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

面試題二:經常使用四種垃圾回收算法

經常使用的垃圾回收算法有四種:標記-清除算法、複製算法、標記-整理算法、分代收集算法。

1.標記-清除算法

分爲標記和清除兩個階段,首先標記出全部須要回收的對象,標記完成後統一回收全部被標記的對象,以下圖。

缺點:標記和清除兩個過程效率都不高;標記清除以後會產生大量不連續的內存碎片。

JVM的判斷對象是否已死和四種垃圾回收算法總結

2.複製算法

把內存分爲大小相等的兩塊,每次存儲只用其中一塊,當這一塊用完了,就把存活的對象所有複製到另外一塊上,同時把使用過的這塊內存空間所有清理掉,往復循環,以下圖。

缺點:實際可以使用的內存空間縮小爲原來的一半,比較適合。

JVM的判斷對象是否已死和四種垃圾回收算法總結

3.標記-整理算法

先對可用的對象進行標記,而後全部被標記的對象向一段移動,最後清除可用對象邊界之外的內存,以下圖。

JVM的判斷對象是否已死和四種垃圾回收算法總結

4.分代收集算法

把堆內存分爲新生代和老年代,新生代又分爲 Eden 區、From Survivor 和 To Survivor。通常新生代中的對象基本上都是朝生夕滅的,每次只有少許對象存活,所以採用複製算法,只須要複製那些少許存活的對象就能夠完成垃圾收集;老年代中的對象存活率較高,就採用標記-清除和標記-整理算法來進行回收。

JVM的判斷對象是否已死和四種垃圾回收算法總結

在這些區域的垃圾回收大概有以下幾種狀況:

大多數狀況下,新的對象都分配在Eden區,當 Eden 區沒有空間進行分配時,將進行一次 Minor GC,清理 Eden 區中的無用對象。清理後,Eden 和 From Survivor 中的存活對象若是小於To Survivor 的可用空間則進入To Survivor,不然直接進入老年代);Eden 和 From Survivor 中還存活且可以進入 To Survivor 的對象年齡增長 1 歲(虛擬機爲每一個對象定義了一個年齡計數器,每執行一次 Minor GC 年齡加 1),當存活對象的年齡到達必定程度(默認 15 歲)後進入老年代,能夠經過 -XX:MaxTenuringThreshold 來設置年齡的值。

當進行了 Minor GC 後,Eden 還不足覺得新對象分配空間(那這個新對象確定很大),新對象直接進入老年代。

佔 To Survivor 空間一半以上且年齡相等的對象,大於等於該年齡的對象直接進入老年代,好比 Survivor 空間是 10M,有幾個年齡爲 4 的對象佔用總空間已經超過 5M,則年齡大於等於 4 的對象都直接進入老年代,不須要等到 MaxTenuringThreshold 指定的歲數。

在進行 Minor GC 以前,會判斷老年代最大連續可用空間是否大於新生代全部對象總空間,若是大於,說明 Minor GC 是安全的,不然會判斷是否容許擔保失敗,若是容許,判斷老年代最大連續可用空間是否大於歷次晉升到老年代的對象的平均大小,若是大於,則執行 Minor GC,不然執行 Full GC。

當在 java 代碼裏直接調用 System.gc() 時,會建議 JVM 進行 Full GC,但通常狀況下都會觸發 Full GC,通常不建議使用,儘可能讓虛擬機本身管理 GC 的策略。

永久代(方法區)中用於存放類信息,jdk1.6 及以前的版本永久代中還存儲常量、靜態變量等,當永久代的空間不足時,也會觸發 Full GC,若是通過 Full GC 還沒法知足永久代存放新數據的需求,就會拋出永久代的內存溢出異常。

大對象(須要大量連續內存的對象)例如很長的數組,會直接進入老年代,若是老年代沒有足夠的連續大空間來存放,則會進行 Full GC。

相關文章
相關標籤/搜索