JVM垃圾收集子系統(七大垃圾收集器+垃圾收集算法+引用計數/可達性分析+finalize())

Serial收集器(新生代收集器):

出現最早,資格最老的一個垃圾收集器。
單線程的收集器,所有用戶線程執行到這裏,都會暫停,執行垃圾回收的保護線程。

缺點:會造成卡頓,用戶體驗效果差,分配內存空間大的場景下回收效率差,卡頓時間長。
優點:單線程省去了切換線程的開銷,內存較小的應用回收效率較高。
一般應用於客戶端桌面應用的垃圾回收。
回收算法:複製算法 或者稱爲 標記-複製算法

標記複製算法就是把新生代內存空間劃分爲兩個相等大小的區域,垃圾回收時把正在使用中的帶有引用標記的對象複製到另外一片區域,複製完成後把要回收的區域進行GC內存清理。非常暴力,不會出現碎片化問題。缺點就是對內存空間要求較大,要雙倍的內存空間。(所以才能在廣闊的新生代Eden區使用)

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

它其實Serial的多線程版本,它也是會暫停所有的用戶線程等待GC,但是因爲它是多線程並行執行(注意是並行執行不是併發),所以它的收集效率會比Serial要高,所以造成的卡頓時間會很短。

併發與並行:
併發是指單CPU中,交替輪換着執行多個任務,表面上的同時進行實際上是交替進行的。
並行是指多核CPU中,每個CPU都在運行一個程序,真正意義上的同時執行。

缺點:如果是單核CPU環境下會有線程切換問題,效率反而比Serial要低。
優點:多核CPU環境下充分利用多核優勢,縮短了垃圾回收的時間,縮短了卡頓時間。
回收算法(和Serial一樣的):複製算法 或者稱爲 標記-複製算法

標記複製算法就是把新生代內存空間劃分爲兩個相等大小的區域,垃圾回收時把正在使用中的帶有引用標記的對象複製到另外一片區域,複製完成後把要回收的區域進行GC內存清理。非常暴力,不會出現碎片化問題。缺點就是對內存空間要求較大,要雙倍的內存空間。(所以才能在廣闊的新生代Eden區使用)

Parallel Scavenge收集器(新生代收集器):

一樣是在新生代內存中工作的垃圾收集器,一樣是複製算法,一樣是多線程並行收集器,這個可以說是ParNew的升級版。它升級的內容有:
(1)可供我們設置垃圾收集的停頓時間,通過設置-XX:MaxGCPauseMillis (毫秒)來規定工作時間,時間一到,垃圾收集打掃工作沒做完也退出,這樣一定程度上能夠保證用戶線程不會被卡頓太久。
(2)可供我們設置垃圾收集器的吞吐量,也就是垃圾回收時間和運行程序總時間的一個比值,通過設置-XX:GCTimeRatio 後,會對原來的回收區域進行劃分,通過吞吐量進行多次回收,每次時間間隔都很短(原來是每次停頓三秒鐘,一分鐘收集一次,現在變成每次停頓0.3秒,一分鐘收集十次,因爲0.3秒用戶不容易察覺停頓了進行垃圾回收),所以同樣是優化了用戶體驗。

Serial Old 收集器(老年代收集器):

這個其實是Serial收集器的老年代版本,也是單線程收集器。
回收算法是:標記整理:

標記整理算法是結合了標記清除和標記複製兩個算法的優點,它還是會按照引用來標記存貨對象,然後整理就是把存活對象壓縮到堆中的一塊,並按順序排放,同時把堆的其它全部對象清除。

爲什麼不用跟Serial老大哥一樣的標記複製呢?就像前面講的那樣,老年代的內存空間小,而且存活對象生命週期長,假如用了標記清除,每一次的GC都會複製一遍,首先就沒有那麼大的空間,其次大部分對象都是存活甚至是全部對象存活,那麼複製就非常浪費時間並且是做無用功。

Parallel Old 收集器(老年代收集器):

很明顯是Parallel Scavenge的老年代版本啦,一樣的多線程並行,一樣的可設置吞吐量和收集工作時間。
算法是標記整理,具體的原因跟上面一樣。

CMS收集器(老年代收集器):

它的簡寫其實是:Concurrent Mark Sweep , 翻譯過來就是 併發標記清除。
所以,它就比較清晰了,它是併發的,算法採用的是標記清除算法。

標記清除算法就是從根結點開始標記所有被引用的對象,然後遍歷堆,將沒有引用標記的對象清除掉。

分爲四個步驟進行垃圾回收:

(1)初始標記:利用可達性分析法對可達對象進行標記,這一步是單線程執行,用戶線程有停頓。
(2)併發標記:初始標記後,這一步是對已有標記的對象進行併發跟蹤,執行時間長,但是用戶線程不需要停頓。
(3)重新標記:併發標記跟蹤過程中,產生的新垃圾對象,也就是跟蹤標記過程中失去了標記引用的對象,都識別爲可回收對象,這裏進行重新標記,也是單線程的,需要用戶線程有停頓。
(4)併發清理:併發清楚標記的垃圾對象,不需要停頓。

優點顯而易見就是把標記和清除的耗費時間的過程都做併發處理,靠着多核CPU優勢將用戶卡頓減至最小。
缺點就是標記清除算法的通病就是內存碎片化問題嚴重。並且單核環境下的併發會佔用大量資源導致性能很低。

G1收集器:

這是一款JDK1.7後引用的一款很強大的垃圾回收,它打破了之前嚴格劃分的新生代老年代的內存空間的嚴格劃分,它是能同時管理全部內存空間的一款垃圾收集器,可以想象成一塊畫布上無數個顏料塊,黑色代表老年代,白色代表新生代,雜亂無章的揉雜再一起,而它爲了區分,會有一張remember set的表,記錄每一塊顏料塊裏那個對象的引用情況,並且它會預測停頓時間,並且優先去回收效率最高的那一塊內存。

它會利用之前CMS收集器一樣的執行步驟:
(1)初始標記
(2)併發標記
(3)最終標記
(4)清除回收(CMS在這裏是直接回收,而G1還會進行分析評估,去選擇性回收最大收益的一塊內存)

所以G1 的優點包括了CMS收集器的優點,併發與並行同在,老年代與新生代沒有嚴格劃分,有利於內存空間整合(也就是清除一塊原來是老年代對象的內存後,可以放入新生代對象),同時也解決了碎片化問題(回收掉之後,會融入原來的零散的內存,方便下一次大對象的存入),並且在回收時做到可預測的停頓。但是它通過remember set表上的記錄信息,對不同生命週期的對象回收還是有不同的回收模式。

最後,分析對象是否要被回收上(也就是判斷對象是否存活),有兩個比較主要的判斷方法:

(1)引用計數法:

很簡單就是一個對象有一個引用,每被引用一次就增加一個引用計數,刪除一個引用就減少一個引用計數,在GC的時候只回收引用計數爲0 的對象。
但是假如有三個類,ABC,A引用B,B引用C,C引用A,那這就是循環引用,這三個對象互相循環,導致引用都不爲0;但是對於外界來說它們三個都已經是不可用的了,導致三塊內存都無法被回收,這就是引用計數法的缺陷。

(2)可達性分析法:

這是一個從外由內的分析法,有一個概念叫GC root , 它其實說白了就是外界對某個對象的引用,當一個對象設置爲null時候也就是斷開了引用,引用鏈斷了就證明該對象已經不可達了,即使還有零星幾個引用計數,也要被回收。

這就是有的對象看起來還活着,但是其實它已經死了。

GC Root說白了就是引用,那麼能引用到對象內存的地方都有哪裏?

(1)棧幀,局部變量表中除了基本類型外,還有引用類型。
(2)方法區(元空間),定義爲靜態的對象在加載時候分配內存,同時也就會有引用指向。
(3)本地方法棧(native等非Java語言的方法)。

筆試最喜歡問的 finalize()方法:

finalize()還有個好兄弟finally,都是筆試故意來爲難你的基礎題,異常捕獲機制中的finally我們用過很多,但是finalize()…說實話…
正常業務開發誰會用?你用嗎?我不用,你用嗎?呵,正經業務開發誰會用。講究!
finalize()是Object中的方法,所有對象在被回收臨死前,都會調用一次這個方法,處理一些回收內存前必須進行的工作(例如你可以重寫finalize方法,在某個對象被回收前打印一句“額我死了!”),你也可以在臨死前最後一刻給該對象又加回一個引用,讓該對象不被回收,但是這些都是花裏胡哨的沒什麼用。真的要用這個對象,保持住它的外部引用不就可以了嗎?所以這基本上算是一個規範約束。沒其他特別的含義,就做一個通知,然後就被回收。

以上就是我的個人總結,鞠躬!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章