衆所周知,Java有本身的垃圾回收機制,它能夠有效的釋放系統資源,提升系統的運行效率。那麼它是怎麼運行的呢,此次就來詳細解析下Java的垃圾回收html
垃圾回收回收的天然是垃圾,那麼java中是垃圾指的是什麼呢?java中的垃圾指的是內存中再也不使用的對象,這些對象再也不會被使用,可是依然存在於內存中,若是不及時清理,長此以往會使得內存中存在大量的無用對象,顯然會影響系統的正常運行java
既然知道了內存中再也不使用的對象就是垃圾,那麼JVM是如何從堆內存中找到這些垃圾的呢?主要有兩種方法:算法
引用計數算法是垃圾收集器比較早的方法,堆內存中的全部對象實例都有一個引用計數,當任何其餘變量被賦值爲這個對象的引用時,這個對象的計數就+1,當一個對象實例
的某個引用超過了生命週期或被設置爲其餘新值時,對象的實例引用計數-1,當對象實例的引用計數爲0時,就會被當作垃圾回收,以下示例
public static void gcTest1(){
User user = new User();//建立一個User對象實例,並新建User變量引用這個對象,此時引用計數+1以後=1
user = new User();//user變量執行了一個新的User對象實例,上一行中建立的User對象實例引用計數-1=0,會被垃圾回收
}工具
可是引用計數算法會有循環引用的問題,如
User user1 = new User();
User user2 = new User();
user1.user = user2;
user2.user = user1;spa
因爲user1和user2互相引用對方,致使他們的引用計數器始終不會爲0,這樣垃圾收集器就永遠沒法回收它們。設計
可達性分析算法是將全部的引用關係看作一張圖,從GC Root節點開始尋找對應的引用節點,找到節點以後繼續尋找這個節點的引用節點,當全部的引用節點尋找完後,剩餘的沒有被尋找到的節點就是不可達節點,不可達的節點就會被斷定爲能夠回收的對象,能夠做爲GC ROOT節點的對象由四種分別以下:
a.虛擬機棧中引用的對象(棧幀中的本地變量表)
b.方法區中類靜態屬性引用的對象
c.方法區中常量引用的對象
d.本地方法棧中的JNI(Native方法)引用的對象htm
而即便可達性分析算法中剩下的不可達對象也並不是直接被垃圾回收,而只是暫時處於「緩刑」階段,真正被垃圾回收至少還須要經歷兩次標記的過程
第一次標記:當對象通過可達性分析被斷定位不可達對象以後,會被第一次標記
第二次標記:第一次標記以後,會繼續判斷該對象是否有必要執行finalize()方法,在finalize()方法中沒有從新與引用鏈創建關聯關係的會被第二次標記對象
被第二次標記以後的對象就會被垃圾收集器進行垃圾回收處理blog
上一段是關於怎麼尋找垃圾的,那麼找到垃圾以後須要怎麼進行回收呢,主要分爲四種收集算法生命週期
標記-清除算法是從全部的GC-ROOT集合進行掃描,對可達的對象進行標記表示是存活的,標記完後再掃描整個內存中未被標記對象進行回收。標記-清除算法只對不存活的對象進行處理,而對於存活的對象不作任何處理,在存活對象較多的狀況下效率較高,由於只須要操做少許的不存活的對象便可,可是同時也會形成內存碎片,所謂內存碎片是指內存中連續空間比較小的內存,以下圖示:(綠色區域爲存活對象,紅色區域爲垃圾對象)
經過標記-清除算法進行垃圾回收以後,將垃圾對象進行清除,此時內存地址爲3和內存地址爲7的位置是空閒的,若是此時新建一個對象須要佔據內存爲2,雖然位置3和位置7位置是空閒的,且加起來空閒內存也爲2,可是因爲不是連續的顯然沒法知足新對象的需求,因此新對象須要在地址12及以後來給它分配內存,而若是以後都不會再有內存爲1的對象建立的話,那麼內存地址3和7的位置就會一直沒法獲得使用,這兩個位置就會被稱做爲內存碎片
因此這種算法的優勢是操做簡便,適用於存活對象較多,垃圾對象較少的狀況,可是缺點是內存碎片化嚴重,
複製算法是將內存劃分爲兩塊大小相等的內存空間,每次只使用一塊,當被使用的這塊內存滿了以後將這塊內存上存活的對象複製到另外一塊內存上,而後而後再將這塊內存清除掉,以下圖
因爲是將所要存活的對象依次複製到另外一塊內存,因此不會再有內存碎片問題,能夠頗有效的使用內存,可是這種算法須要將內存一分爲二,一半可用而另外一半必須是空閒狀態。
並且若是內存中的存活的對象不少的話,須要每一個存活對象都進行復制一次,效率會比較低,比較適用於存活對象較少而垃圾對象較多的場景
標記-整理算法是結合了以上兩種算法優勢而設計出來的,開始和標記-清除算法同樣,先將全部須要回收的垃圾對象進行標記,而後不是進行清除,而是將存活對象移動到內存的一端,而後清除
端邊界外的對象,以下圖示:
很顯然這種算法既不會出現內存碎片,同時又不須要將內存一分爲二,同時知足了標記-清除算法和複製算法的優勢。惟一的缺點就是須要將所要對象的位置進行移動
分代收集算法是目前大部分JVM所採用的垃圾收集方法,它的核心思想是根據對象存活的生命週期將內存劃分爲若干個不一樣的區域。通常區域分爲老生代(Old generation)和新生代(Young generation)
老生代的特色是每次垃圾回收時只要少許的對象須要被回收
新生代的特色是每次垃圾回收時都有大量的對象須要被回收
而針對不一樣區域再選擇不一樣的收集算法進行垃圾收集。
新生代區域的內存劃分是將內存按8:1:1的比例將內存劃分爲一個較大Eden區域和兩個較小的Survivor區域,而兩個Survivor區域分別叫From Space和To Space,以下圖
對象的內存使用只會用到Eden區和其中的一個Survivor區,當進行垃圾回收時,會將Eden區和使用的Survivor區(From Space)中的全部存活的對象複製到另外一塊Survivor區(To Space)中去,此時Eden區和Survivor(From Space)的內存會所有清空,而只有另外一塊Survivor(To Space)會被使用,而後系統在運行一段時間以後,Eden區又會有不少對象,再進行垃圾回收的時候,Eden區會和以前保存數據的Survivor區(To Space)再將全部存活的對象複製到以前清空的Survivor區(From Space),而此時Eden和Survivor(To Space)又會被清空,反覆這樣操做,直到其中一塊Survivor滿了沒法再存放對象的時候,就會直接把存活對象存放到老年代去,同時在新生代通過了15次垃圾回收後還依然存活的對象,也會被放到老年代。而老年代若是內存已經滿了,則再觸發一次垃圾回收(Full GC),Full GC的頻率比較低,由於老年代中存活的對象通常生命週期都比較長。而在新生代觸發的垃圾回收叫Minor GC,Minor GC觸發的頻率較高,由於新生代存放的大多數生命週期較短的對象。
針對新生代和老年代的特色,垃圾回收的算法也不同,新生代存活對象較少,採用的是複製算法,老年代存活對象較多,採用的是標記-整理算法。分代收集算法以下圖示:
當老年代區域滿了的時候就會觸發一次Full GC,不過因爲老年代中存儲的對象生命週期通常都比較長,因此Full GC的頻率會比較低,回收的對象也比較少
如今知道了Java中的垃圾是什麼,垃圾如何尋找以及使用何種算法及方式進行垃圾回收,那麼垃圾回收的工具是什麼呢?Java虛擬機靠的是垃圾收集器來進行垃圾回收,詳情參看下一篇
tips:以上分析的都是針對堆內存的垃圾回收,實際上方法區也是會有垃圾回收的出現,好比廢棄的常量以及沒有被使用的類信息。
廢棄的常量被垃圾回收的條件就是沒有被引用;
類信息被垃圾回收的前提是:1.該類的全部實例已經被回收 2.加載該類的ClassLoader已經被回收 3.該類對應的Class對象在任何地方沒有被引用,確保沒法經過反射來訪問該類的方法。