1960年誕生於MIT的Lisp是第一門真正使用內存動態分配和垃圾收集技術的語言。
Java的垃圾收集(Garbage Collection)主要關注堆和方法區的內存回收。
在GC堆進行回收前,第一件事情就是要肯定哪些對象還活着,哪些對象已經死亡,須要被回收。
判斷對象是否存活的算法:
1)引用計數器(Reference Counting)【Java的GC不使用此算法】:
給對象添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值減1,計數器爲0的對象就是不可能再被使用的。
使用者:微軟COM技術、使用ActionScript3的FlashPlayer、Python、Squirrel、……
缺陷:很難解決對象之間的互相循環引用問題。
2)根搜索算法(GC Roots Tracing)【Java的GC使用此算法】:
經過一系列的名爲「GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索全部走過的路徑成爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可用的。
使用者:Java、C#、Lisp、……
在Java語言裏,能夠做爲GC Roots的對象包括:
a)虛擬機棧中的引用對象。
b)方法區中的類靜態屬性引用的對象。
c)方法區中的常量引用的對象。
d)本地方法棧中JNI的引用的對象。
引用類型(Reference):
引用分爲強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference) 、虛引用(Phanton Reference),引用強度依次逐漸減弱。
強引用:廣泛存在的引用,GC永遠不會回收強引用的對象。
軟引用:還有用,但並不是必須的對象,在系統將要發生內存溢出時將此類對象列進回收範圍並進行第二次回收。
弱引用:非必須的對象,只能生存到下一次垃圾收集發生以前。
虛引用:沒法經過虛引用獲取到對象,爲一個對象設置虛引用關聯的惟一目的就是但願能在這個對象被收集器回收時收到一個系統通知。
要真正宣告一個對象死亡,至少要經歷兩次標記過程:若是對象在進行根搜索後發現沒有與GC Roots相連的引用鏈,那它將會被第一次標記而且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。若是對象被判斷爲有必要執行finalize()方法,那麼這個對象將會被放置在一個名爲F-Queue的隊列中,並在稍後由一條由虛擬機自動創建的、低優先級的finalizer線程去執行。
垃圾收集算法:
a)標記-清除算法(Mark-Sweep)是最基礎的收集算法,分「標記」和「清除」兩個階段。
缺點:效率問題,標記和清除過程的效率都不高;空間問題,標記清除後會產生大量不連續的內存碎片。
b)複製算法(Copying)將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活的對象複製到另一塊上面,而後把已使用過的內存空間一次清理掉。
內存代價過高。商業虛擬機都採用這種收集算法,IBM研究代表,新生代中的對象98%是朝生夕死的,因此並不須要安裝1:1的比例來劃份內存空間。
c)標記-整理算法(Mark-Compact)讓因此存活的對象都向一端移動,而後直接清理掉端邊界意外的內存。
d)分代收集算法(Generational Collection)根據對象的存活週期不一樣將內存劃分爲幾塊,通常是把Java堆分爲新生代和老年代,根據各個年代的特色採用最適當的收集算法。
垃圾收集器(基於Sun HotSpot_1.6_u22):
若是說垃圾收集算法是內存回收的方法論,而垃圾收集器就是內容回收的具體實現。
Young Generation:Serial、ParNew、Parallel Scavenge。
Tenured Generation:CMS、Serial Old(MSC)、Parallel Old。
Both:G1。
內存分配與回收策略:
對象優先在Eden區分配。
大對象直接進入老年代。
長期存活的對象將進入老年代。