垃圾收集(Garbage Collection,GC)須要考慮3件事:java
一、哪些內存須要回收算法
二、何時回收spa
三、如何回收線程
Java內存運行時區域中,程序計數器、虛擬機棧、本地方法棧3個區域生命週期與線程相同,這幾個區域的內存分配和回收都具有肯定性,不須要考慮回收的問題,在方法結束或線程結束後內存天然跟着回收。Java堆和方法區在運行期才能知道建立哪些對象,這部份內存的分配和回收是動態的,,垃圾收集器關注的是這部份內存。code
在堆中存放着大部分的對象實例,在回收內存前需斷定這些對象哪些還存活着。對象
引用計數算法:給對象中添加一個引用計數器,每當有一個地方引用它時,計數器加1,當引用失效時則計數器值減1,任什麼時候刻計數器爲0的對象就是不可能再被使用的。blog
主流JVM沒有選用引用計數算法來管理內存,最主要的緣由是它很難解決對象之間互相循環引用的問題:生命週期
1 public class Test1 { 2 3 public Object instance = null; 4 5 public static void main(String[] args) { 6 Test1 objA = new Test1(); 7 Test1 objB = new Test1(); 8 objA.instance = objB; 9 objB.instance = objA; 10 objA = null; 11 objB = null; 12 } 13 }
若是使用引用計數器,對於objA指向的對象,在代碼第6行計數器加1,第9行計數器加1,第10行計數器減1,這樣雖然這個對象以後不會再被使用到,但引用計數器沒法通知GC收集器回收內存。隊列
基本思路:經過一系列的稱爲"GC Roots"的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,即GC Roots到這個對象不可達時,說明此對象是不可用的。內存
在Java中,可做爲GC Roots的對象包括:
一、虛擬機棧(棧幀中的本地變量表)中引用的對象
二、方法區中類靜態屬性引用的對象
三、方法區中常量引用的對象
四、本地方法棧中JNI(Native方法)引用的對象
在引用計數算法的那個示例中,objA指向的對象一開始是被objA這個引用使用,由於objA是在本地變量表中,因此objA指向的對象是GC Roots的對象。以後objA再也不指向那個對象,那個對象只被objB.instance引用,由於這個對象沒有了到GC Roots的引用鏈,因此能夠被回收。
判斷對象是否存活與"引用"有關,在JDK1.2以後,Java對引用的概念進行了擴充,將引用按強度依次逐漸減弱分爲4種:
一、強引用:在程序中相似"Object obj = new Object()"這類的引用,只要強引用海存在,垃圾收集器永遠不會回收掉被引用的對象。
二、軟引用:描述一些還有用但並不是必需的對象。在系統將要發生內存溢出異常以前,將會把這些對象列進回收範圍進行第二次回收。
三、弱引用:也是描述非必需對象的,可是強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次垃圾收集發生以前。
四、虛引用:最弱的一種引用關係,沒法經過虛引用來取得一個對象實例。
真正宣告一個對象死亡,至少要經歷2次標記過程:對象在可達性分析時發現沒有與GC Roots相鏈接的引用鏈,那它將第一次標記而且進行一次篩選,篩選條件是此對象是否有必要執行finalize()方法。當對象沒有finalize()方法或者這個對象的finalize()方法已經被虛擬機調用過,這2種狀況爲"沒有必要執行"。
若是這個對象沒必需要執行finalize(),則會在下一次回收時被回收內存。若是被斷定爲由必要執行,這個對象會放在一個F-Queue隊列中,在稍後會由一個虛擬機自動創建的、低優先級的Finalizer線程去執行它。這裏執行是指虛擬機會觸發這個對象的finalize()方法,但不會等待它運行結束,由於若是一個對象在finalize()方法中執行緩慢或發生死循環,那麼F-Queue隊列的其餘對象可能永久處於等待,致使整個內存回收系統崩潰。稍後GC將對F-Queue中的對象進行第2次小規模的標記,若是對象從新與引用鏈上的對象創建關聯,那麼能夠避免被回收,finalize()方法是對象逃脫死亡的最後一次機會。
Java虛擬機規範中不要求虛擬機在方法區(或者HotSpot虛擬機中的永久代)實現垃圾收集,並且在永久代的垃圾收集效率是很低的。永久代的垃圾收集主要回收2部份內容:廢棄常量和無用的類。回收廢棄常量與回收Java堆中的對象相似,當常量沒有被引用時就能夠被回收。而判斷類是否無用須要同時知足下面3個條件:
一、該類全部的實例都已經被回收,即Java堆中不存在該類的任何實例。
二、加載該類的ClassLoader已經被回收。
三、該類對應的java.lang.Class對象沒有在任何地方被引用,沒法在任何地方經過反射訪問到該類的方法。
這個是最基礎的收集算法,其餘算法都是基於這種思路並對其不足進行改進而獲得的。算法思想:首先標記全部須要回收的對象,在標記完成後統一回收全部被標記的對象。這個算法主要有2個不足:
一、效率問題,標記和清除的效率都不高
二、空間問題,標記清除後會產生大量不連續的內存碎片
標記-整理算法是根據老年代的特色提出的:首先標記全部須要回收的對象,而後讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存。
根據對象存活週期的不一樣將內存劃分爲幾塊,通常把Java堆分爲新生代和老年代,根據各個年代的特色採用最適當的收集算法,新生代的對象屢次垃圾回收都沒有回收對應的內存會移到老年代中,默認新生代和老年代比例是1:2。新生代中每次垃圾收集都只有少許對象存活,選用複製算法。老年代中由於對象存活率高,使用標記-清理或標記-整理算法來回收。
將可用內存按容量大小劃分爲大小相等的2塊,每次只使用其中的一塊,當這一塊的內存用完了,將還存活的對象複製到另一塊上,而後再把已使用的內存空間一次清理掉。這樣每次都是對整個半區進行內存是收集,內存分配不用考慮內存碎片等複雜狀況。
如今的商用虛擬機使用複製算法來回收新生代,不按照1:1的比例來劃份內存空間,而是分爲8:1:1的Eden:From Survivor:To Survivor3個空間,每次使用Eden和其中一塊Survivor,回收時將Eden和Survivor中還存活的對象一次性複製到另一塊Survivor空間上,最後清理Eden和用過的Survivor對象。不能保證每次回收都只有很少於10%的對象存活,當Survivor空間不夠時,須要依賴老年代進行分配擔保。
爲何分爲2塊Survivor對象,若是隻有Eden和一塊Survivor,按照8:2的比例劃分,那麼當使用Survivor存儲對象的時候,內存很快就滿了,須要進行下一次的GC。劃分爲2塊Survivor,Eden能夠一直使用。