什麼是垃圾回收?
垃圾回收(Garbage Collection,GC),顧名思義就是釋放垃圾佔用的空間,防止內存泄露。有效的使用可以使用的內存,對內存堆中已經死亡的或者長時間沒有使用的對象進行清除和回收。
垃圾回收的常見算法:
引用計數法、標記清除法、標記壓縮法、複製算法、分代算法
引用計數法:
給每個對象設置一個計數器,當有地方引用該對象時計數器+1,引用失效時計數器-1。用計數器是否爲0來判斷該對象是否可被回收。
優點:
-
實時性較高,不用等到內存不夠時纔開始進行垃圾回收,運行時可以根據計數器是否爲0來回收。
-
在垃圾回收的過程中,應用無需掛起,如果申請內存時內存不足直接報outofmember錯誤。
-
區域性,更新對象的計數器時,只是影響到該對象,不會掃描全部對象。
缺點:
-
每次對象被引用時,都需要更新計數器有時間開銷
-
浪費CPU資源,即使內存夠用,仍然在運行時進行計數器的統計
-
無法解決循環引用的問題(最大的缺點)
我們來看下面這個例子:
public class ReferenceCountingGC {
public Object instance;
public ReferenceCountingGC(String name) {
}
public static void testGC(){
ReferenceCountingGC a = new ReferenceCountingGC("objA");
ReferenceCountingGC b = new ReferenceCountingGC("objB");
a.instance = b;
b.instance = a;
a = null;
b = null;
}
}
最後這兩個對象都不能被訪問了,但是由於他們相互引用導致計數器不爲0,通過引用計數法永遠無法通知GC收集器回收。
標記清除法
兩個階段:標記、清除
標記清除算法(Mark-Sweep)是最基礎的一種垃圾回收算法,它分爲兩個階段。先把內存中的對象進行標記,哪些屬於可回收的標記出來,然後把這些垃圾清理掉,如上圖所示。清理掉的內存空間就會變爲未使用的內存區域。但是問題也很顯而易見,存在內存碎片。
優點:
-
解決了引用計算算法中的循環引用的問題,沒有從root節點引用的對象都會被回收
缺點:
-
效率較低,標記和清除兩個動作都要遍歷所有的對象,並且在GC是,需要停止應用程序,對與交互性要求比較高的應用而言這個體驗是非常差的。
-
碎片化嚴重
標記壓縮算法
標記壓縮算法是在標記清除算法的基礎之上做了優化的算法。和標記清除算法一樣,從根節點開始對對象的引用進行標記,然後將存活的對象壓縮到內存的一端,然後清理邊界以外的垃圾,從而解決了碎片化的問題。但是標記整理算法對內存變動更頻繁,需要整理所有存活對象的引用地址,在效率上比複製算法要差很多。
複製算法
複製算法的核心就是,將內存空間化爲等分的兩塊,每次只使用一塊,垃圾回收時將正在使用的對象複製到另一個內存空間中,然後將該內存空間清空,交換兩個內存的角色,完成垃圾回收。
這樣使得每次都是對其中的一塊進行內存回收,內存分配時也就不用考慮內存碎片等複雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。
缺點:
-
效率問題: 在對象存活率較高時,複製操作次數多,效率降低
-
空間問題:內存縮小了一半;需要額外空間做分配擔保(老年代)
jvm中年輕代的內存空間使用的就是複製算法
分代算法
前面介紹的每一種算法都有自己的優缺點,誰都不能替代誰,所以根據垃圾回收對象的特點進行選擇纔是明智的選擇。
分代算法中,把Java堆分爲新生代和老年代,年輕代使用複製算法,老年代適合使用標記清除算法或者標記壓縮算法。