前邊講到 JVM 運行時內存的地方,關於新生代、老年代中 GC 垃圾回收以及垃圾回收算法,不知是否有點懵懵的,這篇一塊兒瞭解一下垃圾回收以及垃圾回收算法。java
斷定爲 "死" 對象,或者無用對象時即視爲可回收內存。算法
如何斷定爲垃圾對象,在這有兩個方法。微信
在 Java 中,引用和對象是有關聯的。若是要操做對象則必須用引用進行。所以,很顯然一個簡單的辦法是經過引用計數來判斷一個對象是否能夠回收。
簡單說,即一個對象若是沒有任何與之關聯的引用,即他們的引用計數都不爲 0,則說明對象不太可能再被用到,那麼這個對象就是可回收對象。app
這種方法的效率很是的高,可是卻有一個很大的缺點,沒法解決相互引用的對象,致使進入計數的死循環,導致引用計數算法沒法通知 GC 收集器回收他們。jvm
由於這種弊端,在主流的 Java 虛擬機裏面沒有選用計數算法來管理內存的。spa
爲了解決引用計數法的循環引用問題,Java 使用了可達性分析的方法。經過一系列的「GC roots」對象做爲起點搜索。若是在「GC roots」和一個對象之間沒有可達路徑,則稱該對象是不可達的。orm
要注意的是,不可達對象不等價於可回收對象,不可達對象變爲可回收對象至少要通過兩次標記過程。兩次標記後仍然是可回收對象,則將面臨回收。對象
該算法爲最基礎的垃圾收集算法,如同名稱同樣,該算法分爲 "標記" 和 "清除" 兩個階段。blog
標記階段標記出全部須要回收的對象,清除階段回收被標記的對象所佔用的空間。如圖生命週期
從圖中咱們就能夠發現,該算法最大的問題是內存碎片化嚴重,空間碎片太多,內存不足時,很容易觸發下一次 GC。
爲了解決 Mark-Sweep 算法內存碎片化的缺陷而被提出的算法。按內存容量將內存劃分爲等大小的兩塊。每次只使用其中一塊,當這一塊用完了,就將還存活的對象複製到另外一塊上面,而後再把已使用的內存空間一次性清理掉,每次都對整個半區進行內存回收,不須要考慮碎片問題,如圖:
這種算法雖然實現簡單,內存效率高,不易產生碎片,可是最大的問題是可用內存被壓縮到了本來的一半。且存活對象增多的話,Copying 算法的效率會大大下降。
補充:如今商業虛擬機都是採用複製算法來回收新生代,對象在建立時,虛擬機將內存劃分爲一塊較大的 Eden 空間和兩塊較小的 Survivor 空間,每次使用 Eden 和一塊 Survivor , 當回收時,將 eden 和 Survivor 中還存活着的對象一次性的複製到另外一塊 Survivor 空間上,而後清理掉 eden 和剛纔用過的 Survivor 空間。
hotspot 默認 Eden 和 Survivor 比例爲:8:1,也就是隻有 10% 的空間被浪費。
標記整理,跟 標記-清除 算法中的標記過程是同樣的,只是後續不是直接對可回收對象進行清理,而是讓全部存活的對象向一端移動,而後直接清理掉端界邊界之外的內存。如圖:
分代收集法是目前大部分 JVM 所採用的方法,其核心思想是根據對象存活的不一樣生命週期將內存劃分爲不一樣的域,通常狀況下將 GC 堆劃分爲老生代(Tenured/Old Generation)和新生代(Young Generation)。
老生代的特色是每次垃圾回收時只有少許對象須要被回收,新生代的特色是每次垃圾回收時都有大量垃圾須要被回收,所以能夠根據不一樣區域選擇不一樣的算法。
什麼時候回收以前,先了解一下幾種 GC。
以下是幾種 GC 的觸發場景:
Java 堆是垃圾收集器管理的主要區域,該區域又分爲新生代、老年代,而新生代又分爲一個 Eden 區和兩個 Survivor 區,關於對象的建立,優先在 Eden 區中分配,當 Eden 區沒有足夠空間時,虛擬機將觸發一次 Minor GC,因爲大多數對象都是朝生夕滅,因此 Minor GC 很是頻繁,速度也很是快。
Full GC,發生在老年代的 GC,當老年代沒有足夠的空間時即發生 Full GC,發生 Full GC 通常都會有一次 Minor GC,咱們在這將 Full GC 等價於 Major GC,也許會有疑問,Full GC 跟 Major GC 到底有什麼區別呀,因爲許多 Major GC 是由 Full GC 觸發的,因此不少狀況下將這兩種 GC 分離是不太可能的,在這就不繼續填坑了,感興趣的小夥伴能夠自行搜索瞭解。
首先要清楚垃圾回收要乾的三件事,哪些須要回收、如何回收以及什麼時候回收。
經過 "引用計數算法"(不推薦) 或 "可達性分析算法" 來斷定須要回收的對象,知道了須要哪些是須要回收的對象,再經過垃圾回收算法(複製、標記清除、標記整理、分代收集)實現垃圾回收。
補一張圖:
若是文章有錯的地方歡迎指正,你們互相留言交流。習慣在微信看技術文章,想要獲取更多的Java資源的同窗,能夠關注微信公衆號:niceyoo