原文地址http://blog.csdn.net/zhshulin/article/details/50583724
java中將內存的控制交給JVM來實現,方便了JAVA程序猿,當然犧牲了一部分效率,不過總體來看是值得的。那么JVM中是如何設計GC的呢,本文從幾個問題入手,然后分析了一下設計思路,如果有理解錯誤的地方,請批評指正!主要參考了《深入理解JAVA虛擬機》這本書,圖是盜來的,圖的內容和書上一樣。
在JVM的內存模型中,堆內存是JAVA內存區域中最大的一部分,GC主要就是發生在堆中,用來回收那些無用的對象。這樣直接就引申出了第一個問題:什么樣的對象需要被回收?判斷條件是什么?如何判斷?
先談談什么對象需要被回收,OK,我們自己想一想,肯定是沒用的對象需要被回收,對吧?那么如何判斷哪些對象還有用,哪些沒用了呢?一個對象被創建,如果被引用了,那這個對象肯定是有用的對吧,如果引用全失效了,那就是沒用的對象了,需要被回收。基于這個思想,引用計數法誕生了。引用計數算法:這個非常容易理解,給每個對象添加一個引用計數器,對象每被引用一次,引用計數器就+1,引用失效時就-1。那么判斷一個對象是否有用的條件就變成了對這個計數器值得判斷了,如果為0,那么被回收,如果為>0,那么保留。但是這種方式會產生一個問題,就是對象之間的循環引用無法被識別,即使這兩個對象不能被訪問,但是它們之間互相引用著對方,故而計數器肯定>0,那么就不能被回收。JVM中并沒有使用引用計數算法,而是使用了根搜索算法。根搜索算法:這個算法也不難理解,通過條件,選擇一系列的對象成為“GC Roots"對象,然后將”GC Roots"對象作為起始點開始向下搜索,搜索所有走過的路徑成為“引用鏈”。在這個引用鏈上的對象就保留,而如果一個或多個互相引用的對象不在這個引用鏈上,或者說對象到“GC Roots"不可達,那么這些就是無用的對象,都需要被回收。注:Java語言中,可作為GC Roots的對象包括下面幾種:
1) 虛擬機棧(棧幀中的本地變量表)中引用的對象
2) 方法區中類靜態屬性引用的對象
3) 方法區中常量引用的對象
4) 本地方法棧中JNI(即一般說的Native方法)引用的對象
既然根搜索算法需要考慮到對象之間的引用,那么就要說一下JAVA中對象的引用類型了:從JDK1.2之后,Java對引用的概念進行了擴充,將引用分為強引用,軟引用,弱引用,虛引用,這四種引用的強度依次減弱
1) 強引用就是指在程序代碼之中普遍存在的,類似 “Object obj = new Object()” 這類的引用,只要強引用還存在,垃圾回收器永遠不會回收被引用的對象。我們也正是利用這個原理來重現了OOM異常。
2) 軟引用(SoftReference類)是用來描述一些還有用但并非需要的對象,對于軟引用關聯著的對象,在系統將要發生內存異常之前,將會把這些對象列進回收范圍之中進行第二次回收,如果這次回收還沒有足夠的內存,才會拋出內存異常
3) 弱引用(WeakReference類)也是用來描述非必需對象的,被弱引用關聯的對象只能生存到下一次GC發生之前,當垃圾收集器工作時,無論當前內存釋放足夠,都會回收掉只被弱引用關聯的對象
4) 虛引用(PhantomReference類)也稱為幽靈引用或者幻影引用,它是最弱的一種引用關系,一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象實例,對一個對象設置虛引用關聯的唯一目的就是能在這個對象被收集器回收時收到一個系統通知
那么上述內容看完之后想必都知道了什么樣的對象會被GC了吧,那么JVM又是通過什么方式來回收這些內存的呢?下面就需要了解一下垃圾的回收算法了。
標記-清除算法 試著想一想,如果要你要設計一個算法清除滿足收集條件的對象來釋放內存的時候你該怎么做呢?最簡單的是不是就是把需要回收的對象標記一下,然后直接全部回收就行了?照著這個思路就是”標記-清除算法”的思想了,算法分為“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成后統一回收掉所有被標記的對象。想法很簡單,實際也就是這么做的。但是呢,這種方式是不是最好的?有什么缺陷? 想到這里,就需要分析一下了。一個個的標記然后清除,效率高嗎?當然不。看看下圖的標記-清除算法的示意圖,可以發現,標記-清除之后會產生大量的內存碎片,如果碎片太多,當程序運行沒有足夠連續的內存空間來存放大對象的時候,就會不得不提前觸發一次GC。概括來說就是有兩個缺點:效率不高;內存碎片可能導致提前發生GC。 學習算法的童鞋應該都很清楚,效率是很重要的,有時候需要使用空間來換時間提高效率,那么就需要了解一下第二種回收算法了——復制算法。新聞熱點
疑難解答