JVM中GC垃圾回收機制與垃圾回收算法

推薦相關文章:GC算法

1.運行時內存區       

        要想了解GC垃圾回收機制,首先要了解虛擬機中內存分佈以及管理。如下圖所示,運行時數據區分爲方法區、堆、虛擬機棧、本地方法棧和程序計數器等。

每一塊區域解釋如下:

方法區:是線程共享的內存區域,用來存儲類加載的信息、常量、靜態變量、即時編譯器編譯後的代碼等。其中方法區中還有個經常會用到的區域叫做運行時常量池,主要用於存儲一些常量,當創建一個常量時,首先會在運行時常量池查看是否有,有則直接使用,否則重新創建。

堆:堆是最大的一塊內存區域,也是垃圾回收管理的主要區域,主要用於存放對象實例。

程序計時器:線程私有的,每個線程都會分配一個線程計時器,用來表示當前線程執行的字節碼的行號指示器。在多線程中,一個線程執行的時候釋放鎖,另一個線程執行完,再回來執行前面線程的時候,就是通過程序計時器來獲取繼續執行的位置。

虛擬機棧:虛擬機棧主要存儲基本數據類型變量和引用類型變量。其中與堆的區別就是如:Obj obj=new Object();等號左邊則是在虛擬機棧上分配棧區存儲引用類型變量的句柄Obj obj,等號右邊則是存儲對象實例,棧區的句柄是指向堆區的對象實例的,一般通過句柄訪問堆區的對象實例。

本地方法棧:與虛擬機棧意義相似,區別在於虛擬機棧用於使Java方法,而本地方法棧則是針對於Native方法服務。

 

2.內存分配與回收策略

        下圖所示是堆中內存分配示意圖,創建一個對象,首先會在eden區域分配區域,如果內存不夠,就會將年齡大的轉移到Survivor區,當survivor區域存儲不下,則會轉移年老代的。對於一些靜態變量不需要使用對象,直接調用的,則會被放入永生代。一般來說長期存活的對象最終會被存放到年老代,還有一種特殊情況也會被存放到年老代,就是創建大對象時,比如數據這種需要申請連續空間的,如果空間比較大的,則會直接進入年老代。

        在回收過程中,有一個參數比較重要,就是對象的年齡,如果在一次垃圾回收過程中有使用該對象的,則將對象年齡加1,否則減1,當計數爲0,則進行回收,如果年齡達到一定數字則進入老生代。總的來說內存分配機制主要體現在對象創建之後是否仍在使用,已經不使用的則回收,繼續使用的則對其年齡進行更新,達到一定程度,轉移到年老代。

3.垃圾回收算法

1.標記-清除算法

        該算法先標記,後清除,將所有需要回收的算法進行標記,然後清除;這種算法的缺點是:效率比較低;標記清除後會出現大量不連續的內存碎片,這些碎片太多可能會使存儲大對象會觸發GC回收,造成內存浪費以及時間的消耗。

2.複製算法

        複製算法將可用的內存分成兩份,每次使用其中一塊,當這塊回收之後把未回收的複製到另一塊內存中,然後把使用的清除。這種算法運行簡單,解決了標記-清除算法的碎片問題,但是這種算法代價過高,需要將可用內存縮小一半,對象存活率較高時,需要持續的複製工作,效率比較低。--新生代

3.標記整理(壓縮)算法

        標記整理算法是針對複製算法在對象存活率較高時持續複製導致效率較低的缺點進行改進的,該算法是在標記-清除算法基礎上,不直接清理,而是使存活對象往一端遊走,然後清除一端邊界以外的內存,這樣既可以避免不連續空間出現,還可以避免對象存活率較高時的持續複製。這種算法可以避免100%對象存活的極端狀況,因此老年代不能直接使用該算法。所以使用標記-清理-壓縮算法:

  1. 結合使用標記清理算法(Mark-Sweep)和標記壓縮算法(Mark-Compact)
  2. 並不是每次標記清理都會執行壓縮,而是多次執行GC後,纔會執行一次Compact

--用於老年代

4.分代收集算法

         分代收集算法就是目前虛擬機使用的回收算法,它解決了標記整理不適用於老年代的問題,將內存分爲各個年代,在不同年代使用不同的算法,從而使用最合適的算法,新生代存活率低,可以使用複製算法。而老年代對象存活率搞,沒有額外空間對它進行分配擔保,所以只能使用標記清除或者標記整理算法。

5.引用計數法

這也是一個較爲樸素的解決方案,對於內存中的每一個對象都保存它們的引用計數,一個對象給一個初始值15,一次不可達減一,一次可達加一,當某個對象的引用計數減爲0時,就把它所佔用的內存空間回收。 --應用在edn區