1.垃圾對象的判斷html
Java堆中存放着幾乎全部的對象實例,垃圾收集器對堆中的對象進行回收前,要先肯定這些對象是否還有用,斷定對象是否爲垃圾對象有以下算法:java
給對象添加一個引用計數器,每當有一個地方引用它時,計數器值就加1,當引用失效時,計數器值就減1,任什麼時候刻計數器都爲0的對象就是不可能再被使用的。算法
引用計數算法的實現簡單,斷定效率也很高,在大部分狀況下它都是一個不錯的選擇,當Java語言並無選擇這種算法來進行垃圾回收,主要緣由是它很難解決對象之間的相互循環引用問題。緩存
看下面代碼:框架
/** * 虛擬機參數:-verbose:gc */ public class ReferenceCountingGC { private Object instance = null; private static final int _1MB = 1024 * 1024; /** 這個成員屬性惟一的做用就是佔用一點內存 */ private byte[] bigSize = new byte[2 * _1MB]; public static void main(String[] args) { ReferenceCountingGC objectA = new ReferenceCountingGC(); ReferenceCountingGC objectB = new ReferenceCountingGC(); objectA.instance = objectB; objectB.instance = objectA; objectA = null; objectB = null; System.gc(); } }
看下運行結果:佈局
[GC 4417K->288K(61440K), 0.0013498 secs]
[Full GC 288K->194K(61440K), 0.0094790 secs]
看到,兩個對象相互引用着,可是虛擬機仍是把這兩個對象回收掉了,這也說明虛擬機並非經過引用計數法來斷定對象是否存活的。spa
爲了克服引用計數法的弊端,如今比較主流的實現算法是可達性分析算法。Java和C#中都是採用根搜索算法來斷定對象是否存活的。這種算法的基本思路是經過一系列名爲「GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時,就證實此對象是不可用的。.net
具體如圖:線程
在Java語言裏,可做爲GC Roots的對象包括下面幾種:3d
判斷引用是否無效的過程分爲三個階段
(1):當JVM進行垃圾收集時,JVM使用可達性分析算法進行分析,若是對象在進行可達性分析後發現沒有與GC Roots相鏈接的引用鏈,此時該對象將被第一次標記,並進行一次篩選,篩選的條件是此對象有沒有必要執行finalize()方法,若是對象沒有覆蓋該方法,或者該方法已經被虛擬機調用過了,虛擬機將這兩種狀況都視爲「沒有必要執行」。
(2):若是該對象被斷定爲有必要執行finalize()方法,那麼對象將會被放置到一個叫作F-Queue的隊列中,並在稍後由一個由虛擬機自動創建的、低優先級的Finalizer線程去執行它。這裏所謂的執行是指虛擬機會觸發這個方法,但並不承若會等待它運行結束。由於一個對象可能在finalize()方法中執行緩慢,或者發生了死循環,這將致使該隊列中的其餘對象長期處於等待階段,甚至致使整個內存系統的奔潰。
(3):F-Queue中的標記篩選。
finalize()方法是對象逃脫死亡命運的最後一次機會,而後GC將對F-Queue中的對象進行第二次小規模的標記。若是對象在finalize()方法中成功拯救了本身,即與引用鏈上的任何一個對象創建關聯,那麼在第二次標記的時候,該算法將被移出F-Queue的集合,若是對象這個時候尚未逃脫,那基本上它就真的被回收了。
在JDK1.2以前,Java中引用的定義很傳統:若是引用(reference)類型的數據中存儲的數值表明的是另一塊內存的起始地址,就稱這塊內存表明着一個引用。這種定義很純粹,一個對象只有被引用或者沒被引用兩種狀態。咱們但願描述這樣一類對象:當內存空間還足夠時,則能保留在內存中;若是內存空間在進行垃圾收集後仍是很是緊張,則能夠拋棄這些對象。不少系統的緩存功能都符合這樣的引用場景。在JDK1.2以後,Java對引用的概念進行了擴充,將引用分爲強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)四種,引用強度依次減弱。
4.方法區回收
虛擬機規範中不要求方法區必定要實現垃圾回收,並且方法區中進行垃圾回收的效率也確實比較低,可是HotSpot對方法區也是進行回收的,主要回收的是廢棄常量和無用的類兩部分。判斷一個常量是否「廢棄常量」比較簡單,只要當前系統中沒有任何一處引用該常量就行了,可是要斷定一個類是否「無用的類」條件就要苛刻不少,類須要同時知足如下三個條件:
一、該類全部實例都已經被回收,也就是說Java堆中不存在該類的任何實例
二、加載該類的ClassLoader已經被回收
三、該類對應的java.lang.Class對象沒有在任何地方被引用,沒法在任何地方經過反射訪問該類的方法
在大量使用反射、動態代理、CGLib等ByteCode框架、動態生成JSP以及OSGi這類頻繁自定義ClassLoader的場景都須要虛擬機具有類卸載功能,以保證方法區不會溢出。
5.垃圾收集算法
目前比較主流的垃圾收集算法有四種:標記-清除算法、複製算法、標記-整理算法、分代收集算法。
(1):標記-清除(Mark-Sweep)算法
標記-清除算法最基礎的算法,分爲「標記」和「清除」兩個階段:首先標記出全部須要回收的對象,標記完成後統一回收全部被標記的對象。
優勢:簡單,易於實現。
缺點:主要體如今效率和空間,從效率的角度講,標記和清除兩個過程的效率都不高;從空間的角度講,標記清除後會產生大量不連續的內存碎片, 內存碎片太多可能會致使之後程序運行過程當中在須要分配較大對象時,沒法找到足夠的連續內存而不得不提早觸發一次垃圾收集動做。
標記-清除算法執行過程如圖:
(2):複製(Copying)算法
複製算法是爲了解決效率問題而出現的,它將可用的內存分爲大小相等的兩塊,每次只使用其中一塊,當這一塊內存用完了,就將還存活着的對象複製到另一塊上面,而後再把已經使用過的內存空間一次性清理掉。這樣每次只須要對整個半區進行內存回收,內存分配時也不須要考慮內存碎片等複雜狀況,只須要移動指針,按照順序分配便可。
優勢:內存分配時算法不產生內存碎片。
缺點:空間消耗太大,內存被壓縮爲原來的一半。
複製算法的執行過程如圖:
如今的商用虛擬機都採用這種算法來回收新生代,不過研究代表1:1的比例很是不科學,所以新生代的內存被劃分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。每次回收時,將Eden和Survivor中還存活着的對象一次性複製到另一塊Survivor空間上,最後清理掉Eden和剛纔用過的Survivor空間。HotSpot虛擬機默認Eden區和Survivor區的比例爲8:1,意思是每次新生代中可用內存空間爲整個新生代容量的90%。固然,咱們沒有辦法保證每次回收都只有很少於10%的對象存活,當Survivor空間不夠用時,須要依賴老年代進行分配擔保(Handle Promotion)。
(3):標記-整理(Mark-Compact)算法
複製算法在對象存活率較高的場景下要進行大量的複製操做,效率很低。萬一對象100%存活,那麼須要有額外的空間進行分配擔保。老年代都是不易被回收的對象,對象存活率高,所以通常不能直接選用複製算法。根據老年代的特色,有人提出了另一種標記-整理算法,過程與標記-清除算法同樣,分爲兩個過程標記和整理。首先標記出全部須要回收的對象,而後不是直接對可回收對象進行清理,而是讓全部存活對象都向一端移動,而後直接清理掉邊界之外的內存。
優勢:內存分配時算法不產生內存碎片,也比較易於實現。
缺點:算法複雜度大,執行步驟較多。
標記-整理算法的工做過程如圖:
(4):分代收集算法
根據上面的內容,用一張圖歸納一下堆內存的佈局
現代商用虛擬機基本都採用分代收集算法來進行垃圾回收。這種算法實際上是前三種算法的有機結合罷了,根據對象的生命週期的不一樣將內存劃分爲幾塊,而後根據各塊的特色採用最適當的收集算法。通常在Java堆中分爲兩大塊新生代和老年代。
在新生代中,每次垃圾收集時都發現有大批對象死去,只有少許存活,選用:複製算法
在老年代中,由於對象存活率高、沒有額外空間對它進行分配擔保,就必須使用「標記-清除」或者「標記-整理」算法來進行回收。
轉載地址:http://www.cnblogs.com/xrq730/p/4836700.html
http://blog.csdn.net/ns_code/article/details/18076173