判讀一個對象是否“已死”:
引用計數算法:給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器為0的對象就是不可能再被使用的。
至少主流的Java虛擬機里面沒有選用引用計數算法來管理內存,其中最主要的原因是它很難解決對象之間相互循環引用的問題。(一般面試問和教科書上的解釋的都是這個。)
可達性分析算法:在主流的商用程序語言(Java、C#,甚至包括前面提到的古老的Lisp)的主流實現中,都是稱通過可達性分析(Reachability Analysis)來判定對象是否存活的。這個算法的基本思路就是通過一系列的稱為“GC Roots”的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的。
即使在可達性分析算法中不可達的對象,也並非是“非死不可”的,這時候它們暫時處於“緩刑”階段,要真正宣告一個對象死亡,至少要經歷兩次標記過程:如果對象在進行可達性分析后發現沒有與GC Roots相連接的引用鏈,那它將會被第一次標記並且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。當對象沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機調用過,虛擬機將這兩種情況都視為"沒有必要執行"。
無論是通過引用計數算法判斷對象的引用數量,還是通過可達性分析算法判斷對象的引 用鏈是否可達,判定對象是否存活都與“引用”有關。
強引用就是指在程序代碼之中普遍存在的,類似“Object obj=new Object()”這類的引 用,只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象。
軟引用是用來描述一些還有用但並非必需的對象。對於軟引用關聯着的對象,在系統將 要發生內存溢出異常之前,將會把這些對象列進回收范圍之中進行第二次回收。如果這次回 收還沒有足夠的內存,才會拋出內存溢出異常。
String str=new String("abc"); // 強引用
SoftReference<String> softRef=new SoftReference<String>(str); // 軟引用x
str =null; // 對開對象的強引用,轉換成軟引用
softRef.get() //得到str對象,如果str被回收,則返回null
弱引用也是用來描述非必需對象的,但是它的強度比軟引用更弱一些,被弱引用關聯的 對象只能生存到下一次垃圾收集發生之前。當垃圾收集器工作時,無論當前內存是否足夠, 都會回收掉只被弱引用關聯的對象。
虛引用也稱為幽靈引用或者幻影引用,它是最弱的一種引用關系。一個對象是否有虛引 用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象實例。為一 個對象設置虛引用關聯的唯一目的就是能在這個對象被收集器回收時收到一個系統通知。
回收方法區:
在堆中,尤其是在新生代中,常規應用進行一次垃圾收集一般可以 回收70%~95%的空間,而方法區(或者HotSpot虛擬機中的永久代)的垃圾收集效率遠低於此。
永久代的垃圾收集主要回收兩部分內容:廢棄常量和無用的類。
判斷一個無用的類,要同時滿足一下三個條件:
1.該類所有的實例都已經被回收,也就是Java堆中不存在該類的任何實例。
2.加載該類的ClassLoader已經被回收。
3.該類對應的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該 類的方法。
垃圾回收算法:
標記-清除算法:算法分 為“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成后統一回收所有被標記的對象。
缺點:
1.一個是效率問題,標記和清除兩個過程的效率都不高;
2.標記清除之后會產生大量不連續的內存碎片,空間碎片太多可能會導致以后在程 序運行過程中需要分配較大對象時,無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。
復制算法:它將可用內存按容量划分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着 的對象復制到另外一塊上面,然后再把已使用過的內存空間一次清理掉。
缺點:
1.將內存縮小為了原來的一半。
2.復制收集算法在對象存活率較高時就要進行較多的復制操作,效率將會變低。
標記-整理算法:標記過程仍然與“標記-清除”算法一樣,但后續步驟不是直接對可回收對象進行清理,而是讓所有存 活的對象都向一端移動,然后直接清理掉端邊界以外的內存。
分代收集算法:當前商業虛擬機的垃圾收集都采用“分代收集”(Generational Collection)算法,這種算法並沒有什么新的思想,只是根據對象存活周期的不同將內存划分為幾塊。
一般是把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點采用最適當的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用復制算法,只需要付出少量存活對象的復制成本就可以完成收集。而老年代中因為對象存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記—清理”或者“標記—整理”算法來進行回收。
新生代區域特點:
a) 絕大多數新創建的對象都存放在這個區域
b) 此區域一般來說較小而且JVM垃圾回收頻率較高
c) 采用的算法和存放對象特點使得該區域JVM垃圾回收的效率也非常高
老生代區域特點:
a) 將在"新生代"中生存了較長時間的對象轉移過來
b) 區域一般要大一些而且增長的速度相對於"新生代"要慢一些
c) 垃圾回收的執行頻率也會低很多
空間分配擔保:在發生Minor GC之前,虛擬機會先檢查老年代最大可用的連續空間是否大於新生代所有 對象總空間,如果這個條件成立,那么Minor GC可以確保是安全的。如果不成立,則虛擬機 會查看HandlePromotionFailure設置值是否允許擔保失敗。如果允許,那么會繼續檢查老年代 最大可用的連續空間是否大於歷次晉升到老年代對象的平均大小,如果大於,將嘗試着進行 一次Minor GC,盡管這次Minor GC是有風險的;如果小於,或者HandlePromotionFailure設置 不允許冒險,那這時也要改為進行一次Full GC。