GC-垃圾收集算法與關鍵收集器

引言:

在筆者的上兩篇博文中,主要介紹了jvm的結構和對象的“生死”問題。今天主要來說說垃圾收集算法與各種關鍵的收集器,分析比較各種收集算法的優劣。如果時間和篇幅允許的話對內存動態分配做一些解釋,因爲垃圾回收和動態分配是java的兩大基本特性。筆者目前整理的一些blog針對面試都是超高頻出現的。大家可以點擊鏈接:http://blog.csdn.net/u012403290

技術點:

1、新生代GC(Minor GC):
上篇博文中,我介紹了新生代,老年代、永久代的概念,新生代的意思就是在這個羣體中,具有“朝生夕死”的性質。Minor GC就是對新生代進行垃圾收集。因爲新生代的特性,所以MInor GC也會異常頻繁。

2、老年代GC(Full GC/Major GC):
Full GC就是對老年代進行收集,收集的頻率相對較低,而且收集的時間和速度相對新生代GC來說慢了很多(據說是10倍以上)。

3、GC停頓
指在垃圾回收的過程中,要停止所有的用戶線程。爲了防止GC過程中對象的引用還在發生變化。換句話說就是在你打掃房間的時候,不允許別人繼續扔垃圾了。但是,這裏還是要說明一下,很多知道垃圾回收的小夥伴會說在CMS收集器(後面會討論到)的時候幾乎不發生停頓(GC操作可與用戶線程併發)啊?其實不然,再討論生死那篇博文中,我說過“可達性分析算法”判斷對象是否存活,在枚舉根節點的時候還是會發生停頓的。

4、安全點和安全區域
意思就是當程序跑到這個安全點或者安全區域內才能發生垃圾回收。這樣的話系統的性能和數據安全性都能得到比較好的保障。

5、分配擔保機制
是指在內存分配中,如果按照複製算法(下面會說道)進行分配內存的時候,當回收的時候發現把某一塊的存活對象拷貝到另外一塊的時候發現另外一塊的內存不足,這個時候就會觸發分配擔保機制,這些仍舊存活的對象會直接分配到老年代。也就是說你向銀行貸款需要有個擔保人是一樣的,如果你無力償還的時候,需要擔保人進行償還。

回收算法

在介紹回收算法的時候我會比較一下各個算法的優劣:

1、標記清除算法(Mark-Sweep):
最基礎回收算法之一。分爲兩步,第一步先標記出要回收的對象,然後清除所有被標記的對象。這個算法很好理解,但是它存在着極大的缺陷:①效率不高;②因爲如此的回收方式,會打亂內存對象本有的編排順序,容易造成內存碎片和連續的內存不足問題。比如說本來你左邊有一張桌子(一塊內存),本來坐着一個200斤的漢子(一個對象),然後這個漢子離開了(被清除),後來又過來一個妹子坐了你左邊(新對象進入),然後那個位置對於新來的妹子來說大了太多,有很多空間她都不需要(產生內存碎片),這樣一來空間就浪費了。這個時候呢,你右邊的位子本來坐了一個80斤的妹子(一個對象),後來這個妹子走了(對象被清除),後來來了一個200斤的漢子(新對象進入),發現完全坐不下了(沒有足夠大的內存給新對象分配),這個你可能就需要說“朋友,你去那邊坐吧,那邊空(又觸發了一次GC操作)”。不知道這麼說,大家能理解麼?

2、複製算法(copying):
最基礎算法之一。主要是將可用內存劃分爲大小相等的兩塊。在使用的過程中就只使用其中的一塊,如果使用的這一塊被使用完,在GC的過程中把存活對象賦值到另一塊上去,並清理原來那塊。比如說上課的時候上一半,人滿坐不下,老師說大家去隔壁教室上課(GC操作),然後有些愛逃課的小夥伴就乘機溜走了(可回收的對象清除),在新的教室大家按順序一個個坐下來(把存活的對象都複製到另一塊內存),大家座位之間的空位也很合理(沒有了空間碎片和不足的問題)。複製算法的缺陷就在於如果按這麼操作,那麼就會有很大部分的內存在一定時間會被置空,使用率比較低。當然了,在jvm中對於這種的做法是分爲1個Eden區和兩個Survivor區,兩者時間內存大小默認爲8:1。對象進來先存在Eden區,回收的時候回把存活的對象拷貝到另外一塊Survivor區中(有人會問那還有一塊Survivor呢?是這樣的,兩個Survivor只是進行了切換的操作,它沒有座位新來對象的存儲,他只是作爲在GC複製的時候進行對象轉換的作用)。如果在copy的過程中發現另外一塊Survivor的內存不足,則就會觸發分配擔保機制

3、標記整理算法(Mark-Compact):
建立在標記清除算法的基礎之上,在標記操作執行之後,會讓仍舊存活的對象都向着一端移動,然後清理掉仍舊存活對象邊界以後的數據。

4、分代收集算法
根據對象所屬的年齡(也就是新生代和老年代)進行不同的收集,比如說新生代“朝生夕死“的性質就可以用複製算法進行收集,而老年代中對象存活的時間就比較長,那就需要用標記清除或者標記整理來進行回收。

垃圾收集器

下圖是所有的垃圾收集器,橫線上邊爲新生代收集器,橫線下邊爲老年代收集器,橫線上表示既能收集新生代也能收集老年代。各個收集器之間的鏈接表示此兩者可以共同工作。作爲垃圾收集器必須要配合新生代收集和老年代收集共同使用。

這裏寫圖片描述

1、Serial收集器:
新生代收集器,採用複製算法進行垃圾收集。最古老的收集器,Serial表示串行的意思,也就是說該收集器是一個單線程的收集器。它在進行垃圾收集是必須暫停其他所有的用戶線程,GC停頓感很強。但是單線程收集效率很高,它不用考慮線程交互,專心收集垃圾。

2、ParNew:
新生代收集器,採用複製算法進行垃圾收集。該收集器可以多線程進行收集垃圾,相當於Serial收集器的多線程版本。

3、Parallel Scavenge收集器:
新生代收集器,採用複製算法進行垃圾收集。它也是可以進行多線程收集垃圾,但是它多了一個獨特的能力,引入了一個吞吐量(吞吐量=用戶代碼執行時間/(用戶代碼執行時間+GC所用時間))的概念,被稱爲吞吐量優先收集器。它可以自動調節內存分配。

4、Serial Old收集器
老年代收集器,採用標記整理算法進行垃圾收集。和Serial收集器一樣是一個單線程收集器。

5、Parallel Old收集器
老年代收集器,採用標記整理算法進行垃圾收集。是一個多線程垃圾收集器。

6、CMS收集器(Concurrent Mark Sweep):
老年代收集器,採用標記清除算法進行垃圾收集。在前面提到過,它的GC停頓時間非常短,它主要有4個步驟進行垃圾收集:①初始標記;②併發標記;③重新標記;④併發清除。這裏我再描述一下每個狀態的具體情況。在初始標記的時候會產生GC停頓,它是單線程標記。在併發標記的過程中不會產生GC停頓,它可以與用戶操作線程進行併發,並不影響用戶操作,它主要是尋找引用鏈(在談論生死的博客中有提到)的根節點。在重新標記的時候會產生GC停頓,它可以並行操作,主要是修正前面的標記過程中又變化的對象引用。再併發清理階段,可以與用戶操作線程併發處理,不產生GC停頓。仔細看我上面的描述,你會發現有併發和並行兩種概念,切不可混爲一談。併發是GC線程與用戶線程同時操作,而並行的意思是各個GC線程可以同時操作。併發不存在GC停頓,而並行存在GC停頓。

7、G1收集器
據說是迄今爲止最牛逼的收集器。它的GC停頓時間也非常短,主要有4個步驟:①初始標記;②併發標記;③最終標記;④篩選回收。在初始標記中會產生GC停頓,單線程進行標記。在併發標記中不會產生GC停頓,與用戶線程併發操作。在最終標記中,GC線程並行處理,會產生GC停頓。修正前面標記過程中導致對象應用又變化的部分。在篩選回收會產生GC停頓,並行處理。

限於篇幅,GC收集器就寫這麼多。如果各位看官有興趣,可以針對每一個收集器進行深入探究。

對象分配內存

前面引言中提到,內存動態分配與垃圾回收機制是java的兩大特性。簡單說一下對象分配的一些規則:

1、前面在說複製算法的時候提到了Eden區,這個區具有優先分配權利。如果分配在這個區中的內存不足以新對象入住就會發生一次Minor GC操作。

2、如果新進入的對象所需連續的內存非常之大(典型的就是數組),那麼這個對象可能直接進入老年代中。

3、如果在回收機制進行多次回收之後,那些仍舊存活的對象將會進入老年代。有人會說,那麼jvm怎麼會知道這個對象存活了幾次GC呢?是可以知道的,在執行復制清理算法的時候,存活對象每移動一次,它的年齡就會+1,當年齡達到一定程度後就會放入老年代中。

好啦,總結一下這幾天寫的,都是比較理論的東西。貌似很多小夥伴都不感興趣。我想明天是不是要換換口味了,要不明天我們一起研究一下ConcurrentHashMap的數據結構和底層原理?

如果博文存在什麼問題,或者有什麼想法,可以聯繫我呀,下面是我的微信二維碼:
這裏寫圖片描述

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