Java的JVM GC(Garbage Collection)垃圾回收原理機制及算法

ava GC(Garbage Collection)垃圾回收機制,Java VM中,存在自動內存管理和垃圾清理機制。GC機制對JVM(Java Virtual Machine)中的內存進行標記,並確定哪些內存需要回收,根據一定的回收策略,自動的回收內存,永不停息(Nerver Stop)的保證JVM中的內存空間,防止出現內存泄露和溢出問題。Java中不能顯式分配和註銷內存。有些開發者把對象設置爲null或者調用System.gc()顯式清理內存。設置爲null至少沒什麼壞處,但是調用System.gc()會一定程度上影響系統性能。Java開發人員通常無須直接在程序代碼中清理內存,而是由垃圾回收器自動尋找不必要的垃圾對象,並且清理掉它們。


Java GC主要做三件事:
(a)哪些內存需要GC?
(b)何時需要執行GC?
(c)以何策略執行GC?


Java中什麼哪些內存需要GC回收?
JVM會分配一個運行時內存空間。包括5大部分:程序計數器(Program Counter Register)、虛擬機棧(VM Stack)、本地方法棧(Native Method Stack)、方法區(Method Area)、堆(Heap)。其中程序計數器、虛擬機棧、本地方法棧是每個線程私有內存空間,隨線程而生,隨線程而亡。這3個區域內存分配和回收都是確定的,無需考慮內存回收的問題。
但方法區和堆就不同了,一個接口的多個實現類需要的內存可能不一樣,只有在程序運行期間纔會知道會創建哪些對象,這部分內存的分配和回收都是動態的,GC主要關注的是這部分內存。GC主要進行回收的內存是JVM中的方法區和堆,涉及到多線程(指堆)、多個對該對象不同類型的引用(指方法區),纔會涉及GC的回收。
小結:Java GC針對的是JVM中堆和方法區。


Java GC機制啓動之前,需要確定堆內存中哪些對象是存活的,一般有兩種方法:引用計數法和可達性分析法。
引用計數:每個對象有一個引用計數屬性,新增一個引用時計數加1,引用釋放時計數減1,計數爲0時可以回收。引用計數法實現簡單,判定高效,但不能解決對象之間相互引用的問題。
可達性分析(Reachability Analysis):從GC Roots開始向下搜索,搜索所走過的路徑稱爲引用鏈。當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的。通過稱爲 “GC Roots” 的對象作爲起點,從這些節點開始向下搜索,搜索路徑稱爲 “引用鏈(Reference Chain)”,以下對象可作爲GC Roots:
(a)本地變量表中引用的對象
(b)方法區中靜態變量引用的對象
(c)方法區中常量引用的對象
(d)Native方法引用的對象
當一個對象到 GC Roots 沒有任何引用鏈時,意味着該對象可以被回收。
小結:Java GC垃圾回收機制,回收的是已死的Java對象(引用無法可達)。


Java GC垃圾回收算法


(一)標記 -清除算法
   “標記-清除”(Mark-Sweep)算法,如它的名字一樣,算法分爲“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成後統一回收所有被標記的對象。之所以說它是最基礎的內存回收算法,是因爲後續的算法都是基於這種思路、並對其缺點進行改進而得到的。
主要缺點有兩個:一個是效率問題,標記和清除過程的效率都不高;另外一個是空間問題,標記清除之後會產生大量不連續的內存碎片,空間碎片太多可能會導致,當程序在以後的運行過程中需要分配較大對象時無法找到足夠的連續內存時候,不得不提前觸發另一次垃圾收集動作。


(二)複製算法
“複製”(Copying)內存回收算法,它將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另外一塊上面,然後再把已使用過的內存空間一次清理掉。
這樣使得每次都是對其中的一塊進行內存回收,內存分配時也就不用考慮內存碎片等複雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。只是這種算法的代價是將內存縮小爲原來的一半,持續複製長生存期的對象則導致效率降低。


(三)Java GC的分代垃圾回收機制
GC分代回收算法
GC分代的假設:絕大部分對象的生命週期都非常短暫,存活時間短。
“分代回收”(Generational Collection)算法,把Java堆分爲新生代和老年代,這樣就可以根據各個年代的特點採用適當的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用複製算法,只需要付出少量存活對象的複製成本就可以完成收集。而老年代中因爲對象存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記-清理”或“標記-整理”算法來進行回收。


GC垃圾回收器會在下面兩種情況下啓動:
(a)大多數對象會很快變得不可達。
(b)只有很少的由老對象(創建時間較長的對象)指向新生對象的引用。
爲強化這一假設,Java虛擬機在物理上劃分爲兩個邏輯內存代——新生代(Young Generation)和老年代(Old Generation)。新生代(Young Generation): 新生代空間用來保存那些第一次被創建的Java對象,分爲三個空間:
(a)一個伊甸園空間(Eden )
(b)兩個倖存者空間(Survivor )
一共有三個空間,其中包含兩個倖存者空間。每個空間的執行順序如下:
(a)絕大多數剛剛被創建的對象會存放在伊甸園空間。
(b)在伊甸園空間執行了第一次GC之後,存活的對象被移動到其中一個倖存者空間。
(c)此後,在伊甸園空間執行GC之後,存活的對象會被堆積在同一個倖存者空間。
(d)當一個倖存者空間飽和,還在存活的對象會被移動到另一個倖存者空間。之後會清空已經飽和的那個倖存者空間。
(e)在以上的步驟中重複幾次依然存活的對象,就會被移動到老年代。
在新生代中,使用“停止-複製”算法進行內存清理。絕大多數最新被創建的對象會被分配到這裏,由於大部分對象在創建後會很快變得不可到達,所以很多對象被創建在新生代,然後消失。對象從這個區域消失的過程稱爲“Minor GC” 。
老年代(Old Generation): 對象沒有變得不可達,並且從新生代中存活下來,會被拷貝到這裏。其所佔用的空間要比新生代多。也正由於其相對較大的空間,發生在老年代上的GC要比新生代少得多。對象如果在年輕代存活了足夠長的時間而沒有被清理掉(即在幾次Young GC後存活了下來),則會被複制到年老代,年老代的空間一般比年輕代大,能存放更多的對象,在年老代上發生的GC次數也比年輕代少。當年老代內存不足時,將執行Major GC,也叫 Full GC。老年代存儲的對象比年輕代多得多,而且不乏大對象,對老年代進行內存清理時,如果使用停止-複製算法,則相當低效。一般,老年代用的算法是標記-整理算法,即:標記出仍然存活的對象(存在引用的),將所有存活的對象向一端移動,以保證內存的連續。
小結:Java內存分配和回收機制是:分代分配,分代回收。新生代中,每次垃圾收集時都有大批對象死去,只有少量存活,就選用複製算法,只需要付出少量存活對象的複製成本就可以完成收集。老年代中,其存活率較高、沒有額外空間對它進行分配擔保,就應該使用“標記-整理”或“標記-清理”算法進行回收。


Java GC優化永遠是最後一項任務。因爲Java GC將“Stop the World”(串行GC暫時中斷程序執行)。Stop-the-world會在任何一種GC算法中發生。Stop-the-world意味着 JVM 因爲要執行GC而停止了應用程序的執行。當Stop-the-world發生時,除了GC所需的線程以外,所有線程都處於等待狀態,直到GC任務完成。GC優化很多時候就是指減少Stop-the-world發生的時間。GC優化的根本原因,垃圾收集器清除Java創建的對象,GC執行的次數,即需要被垃圾收集器清理的對象個數,與創建對象的數量成正比,因此,應該減少創建對象的數量。
GC優化兩個目的:
(a)將轉移到老年代的對象數量降到最少。對象被創建在伊甸園空間,而後轉化到倖存者空間,最終剩餘的對象被送到老年代。某些比較大的對象會在被創建在伊甸園空間後,直接轉移到老年代空間。老年代空間上的GC處理會比新生代花費更多時間。因此,減少被移到老年代對象的數據可以顯著地減少Full GC的頻率。減少被移到老年代空間的對象數量,可能被誤解爲將對象留在新生代。但是,這是不可能的。取而代之,你可以調整新生代空間的大小。
(b)減少Full GC的執行時間。Full GC執行時間比Minor GC要長很多。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章