引用計(jì)數(shù)算法
給對(duì)象添加一個(gè)計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用他,計(jì)數(shù)器就加1,當(dāng)引用失效時(shí),計(jì)數(shù)器就減1,任何時(shí)刻計(jì)數(shù)器為0的對(duì)象就是不可能再被使用的.
這個(gè)算法的實(shí)現(xiàn)很簡(jiǎn)單,判定效率也很高,在大部分情況下都是不錯(cuò)的算法,很多語言都使用了這個(gè)算法進(jìn)行內(nèi)存管理.但是主流的JAVA虛擬機(jī)里面沒有使用引用計(jì)數(shù)法來管理內(nèi)存的,主要是這個(gè)算法很難解決對(duì)象之間相互循環(huán)引用的問題,比如A和B相互引用,并且沒有其他引用.實(shí)際上這兩個(gè)對(duì)象已經(jīng)無法再被訪問,但是他么互相引用這對(duì)方于是無法被GC回收.
可達(dá)性分析算法
通過一系列被稱為 GC Roots的對(duì)象作為起始點(diǎn),從這些結(jié)點(diǎn)向下搜索,搜索走過的路徑被稱為引用鏈 Reference Chain 當(dāng)一個(gè)對(duì)象到GC Roots沒有任何引用鏈相連時(shí)(不可達(dá))時(shí),則證明此對(duì)象不可用.被判定為可回收對(duì)象
GC Roots對(duì)象包括以下幾種
- 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象
- 方法區(qū)中靜態(tài)屬性引用的對(duì)象
- 方法區(qū)中常量引用的對(duì)象
- 本地方法棧中JNI(Native) 引用的對(duì)象.
再談引用
JDK1.2之后對(duì)引用的概念進(jìn)行了擴(kuò)充,將引用分為強(qiáng)引用,軟引用,弱引用,虛引用.這四種引用強(qiáng)度以此逐漸減弱
- 強(qiáng)引用就是指在代碼中普遍存在的,類似Object obj = new Object(),只要強(qiáng)引用還在,垃圾收集器永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象.
- 軟引用用來描述一些還有用但并非必須的對(duì)象.在系統(tǒng)發(fā)生內(nèi)存溢出異常之前,將在第二次GC回收.SoftReference類來實(shí)現(xiàn)軟引用.
- 弱引用的強(qiáng)度比軟引用更低,無論當(dāng)前內(nèi)存是否足夠,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象.WeakReference來實(shí)現(xiàn)弱引用.
- 虛引用是最弱的引用關(guān)系.一個(gè)對(duì)象是否有虛引用存在完全不會(huì)對(duì)其生存時(shí)間構(gòu)成影響.也無法通過虛引用獲取一個(gè)對(duì)象實(shí)例.為一個(gè)對(duì)象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個(gè)對(duì)象唄收集器回收時(shí)得到一個(gè)系統(tǒng)通知.通過PhantomReference來實(shí)現(xiàn).
死亡過程
即使在可達(dá)性分析算法中不可達(dá)的對(duì)象,也不是馬上就死的.要宣告一個(gè)對(duì)象死亡.至少要經(jīng)歷兩次標(biāo)記過程:如果對(duì)象在可達(dá)性分析后發(fā)現(xiàn)沒有與GC ROOTS相連的引用鏈,那么他將會(huì)被第一次標(biāo)記.并進(jìn)行一次篩選.篩選的條件是此對(duì)象有沒有必要執(zhí)行finalize方法,如果對(duì)象沒有覆蓋finalize方法或者finalize方法已經(jīng)被執(zhí)行過了.虛擬機(jī)將視為"沒有必要執(zhí)行",如果有必要執(zhí)行finalize方法,那么這個(gè)對(duì)象會(huì)被放入F-Queue隊(duì)列中,并在稍后由虛擬機(jī)創(chuàng)建一個(gè)低優(yōu)先級(jí)的Finalizer線程去執(zhí)行他,這里的執(zhí)行時(shí)值虛擬機(jī)會(huì)觸發(fā)這個(gè)方法,但不承諾會(huì)等待他運(yùn)行結(jié)束.稍后會(huì)進(jìn)行第二次小規(guī)模標(biāo)記.被標(biāo)記兩次的對(duì)象基本上他就是真的被回收了.
對(duì)象可以在finalize方法中將this重新賦值給一個(gè)類變量或者屬性獲得一次自救,但是finalize只能被執(zhí)行一次,所以下一次標(biāo)記時(shí)還是會(huì)被回收.
回收方法區(qū)
方法去中主要回收廢棄的常量和無用的類.但是回收效率一般比較低.
- 當(dāng)一個(gè)常量不被任何對(duì)象引用時(shí),如果此時(shí)發(fā)生GC,而且必要的話,這個(gè)常量就會(huì)被清出常量池.
- 回收類需要同時(shí)滿足3個(gè)條件才可以,而且僅僅是可以回收,并不是不適用了就必然回收.
- 改類所有的實(shí)例都已經(jīng)被回收.
- 加載該類的ClassLoader已經(jīng)被回收
- 該類對(duì)應(yīng)的java.lang.Class對(duì)象沒有在任何地方被引用.無法再任何地方通過反射訪問該類的方法.
標(biāo)記清除算法
將要回收的對(duì)象進(jìn)行標(biāo)記,然后統(tǒng)一回收.他的主要不足有兩個(gè):一個(gè)是效率低,另一個(gè)是標(biāo)記清除后會(huì)產(chǎn)生大量的不連續(xù)內(nèi)存碎片.
復(fù)制算法
為了解決效率問題,復(fù)制算法出現(xiàn)了.他可以將內(nèi)存分為兩塊,每次只使用其中的一塊,當(dāng)這一塊內(nèi)存用完了.就將還存活的對(duì)象復(fù)制到另外一塊上面,然后把已經(jīng)使用過的空間一次清理掉.也就不用考慮碎片的問題了.但是每次只用一半的內(nèi)存代價(jià)未免也太高了.
大多數(shù)商業(yè)虛擬機(jī)都是采用這種收集算法來回收新生代的,由于很多對(duì)象都是朝生夕死所以不需要1:1來分配內(nèi)存空間,常用的分配策略是分為Eden和兩個(gè)Survior空間大小比例是8:1:1,只有百分之10會(huì)被浪費(fèi).
標(biāo)記-整理算法
復(fù)制算法在對(duì)象存活率較高的時(shí)候就要進(jìn)行較多的復(fù)制操作,效率將會(huì)變低,更關(guān)鍵的是,如果不想浪費(fèi)50%的空間,就需要有額外的空間進(jìn)行分配擔(dān)保,所以老年代一般不用這種算法.標(biāo)記整理算法讓存活的對(duì)象都想一端移動(dòng),然后直接清理掉端邊界以外的內(nèi)存.
分代收集算法
當(dāng)前的商業(yè)虛擬機(jī)的垃圾收集都采用分代手機(jī)算法,根據(jù)對(duì)象存存活周幾的不同將內(nèi)存劃分為幾塊.一般是分為新生代和老生代,可以根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴?
GC日志
通過-XX:+PrintGCDetails開啟日志,閱讀GC日志沒什么技術(shù)含量,它只是一些人為規(guī)定的規(guī)則,每種日志形式都是由他們自身的實(shí)現(xiàn)所決定的.但虛擬機(jī)設(shè)計(jì)者為了方便用戶閱讀,將各個(gè)收集器的日志都維持一定的共性.
最前面的數(shù)字代表GC發(fā)生的時(shí)間,這個(gè)數(shù)字是JAVA虛擬機(jī)啟動(dòng)以來經(jīng)過的描述.33.125: [GC [DefNew : 3325K->1525K(3712K), 0.0025925 secs] 3324K->152K(11904K), 0.0031680 secs ]
日志開頭的GC 或 Full GC 說明了這次垃圾回收停頓的時(shí)間,如果是Full GC 說明發(fā)生了 Stop the world.如果是System.gc()觸發(fā)的GC會(huì)顯示 Full GC(System)
接下來的 [DefNew [Tenured [Perm 等表示GC發(fā)生的區(qū)域,這里顯示的區(qū)域名稱與使用的回收器名稱是相關(guān)的.例如上面DefNew是使用的Serial收集器,如果是ParNew收集器則會(huì)顯示成ParNew
后面的數(shù)字代表 GC前該已經(jīng)使用的容量-> GC后該區(qū)域的容量(區(qū)域總?cè)萘?
再往后就是GC耗時(shí)了.