文章目錄
- 0、如何判定一個對象的生死?
- 1、上文提到的引用又是什么
- 1、強引用:
- 2、軟引用:
- 3、弱引用:
- 4、虛引用:
- 2、垃圾收集算法
- 1、標記-清除
- 2、標記-復制
- 優化:👇
- 3、標記-整理
0、如何判定一個對象的生死?
引用計數器算法:在JDK1.2之前,使用的是引用計數器算法。
在對象中添加一個引用計數器,每當有地方引用這個對象的時候,引用計數器的值就+1,當引用失效的時候,計數器的值就-1,當引用計數器被減為零的時候,標志著這個對象已經沒有引用了,可以回收了!
使用可達性算法分析:通過 一系列稱為“GC Roots”的根對象作為起始節點集,從這些節點開始,根據引用關系向下搜索,搜索過程所走過的路徑稱為“引用鏈”,如果某個對象到GC Roots間沒有任何引用鏈相連, 或者用圖論的話來說就是從GC Roots到這個對象不可達時,則證明此對象是不可能再被使用的(對象已死)
1、上文提到的引用又是什么
java中將引用分為 強引用、軟引用、弱引用、虛引用。
1、強引用:
強引用是最普遍的一種引用, 如果一個對象具有強引用,那垃圾回收器絕不會回收它。
2、軟引用:
如果一個對象只具有軟引用,則內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高速緩存。
3、弱引用:
只具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。
4、虛引用:
“虛引用”顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用并不會決定對象的生命周期。如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。 為一個對象設置虛引用關聯的唯一目的只是為了能在這個對象被收集器回收時收到一個系統通知。
2、垃圾收集算法
1、標記-清除
標記-清除算法分為兩個階段:標記階段和清除階段。
- 標記階段的任務是標記出所有需要被回收的對象。
- 清除階段就是回收被標記的對象所占用的空間。
標記-清除算法比較基礎,但是主要存在兩個缺點: - 執行效率不穩定,如果 Java 堆中包含大量對象,而且其中大部分是需要被回收的,這時必須進行大量標記和清除的動作,導致標記和清除兩個過程的執行效率都隨對象數量增長而降低。
- 內存空間的碎片化問題,標記、清除之后會產生大量不連續的內存碎片,空間碎片太多可能會導致當以后在程序運行過程中需要分配較大對象時無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。
2、標記-復制
標記-復制算法解決了標記-清除算法面對大量可回收對象時執行效率低的問題。
它將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活著的對象,復制到另外一塊上面,然后再把已使用的內存空間一次清理掉。分配內存時,只要移動堆頂指針,按順序分配即可,這樣一來就不容易出現內存碎片的問題。
這種算法實現簡單,運行高效且不容易產生內存碎片,但是卻對內存空間的使用做出了高昂的代價,因為能夠使用的內存縮減到原來的一半。
新生代垃圾收集主要采用這種算法,因為新生代的存活對象比較少,每次復制的只是少量的存活對象。
優化:👇
實際中,把新生代分為一塊較大的Eden空間和兩塊較小的 Survivor空間,比例是8:1:1,每次分配內存只使用Eden和其中一塊Survivor。發生垃圾收集時,將Eden和其中一塊Survivor中仍然存活的對象一次性復制到另外一塊Survivor空間上,然后直接清理掉Eden和已用過的那塊Survivor空間。新生代的垃圾收集主要采用標記-復制算法,因為新生代的存活對象比較少,大多是“朝生夕死”,每次復制少量的存活對象效率比較高。
3、標記-整理
為了降低內存的消耗,引入一種針對性的算法:標記-整理(Mark-Compact)算法。
其中的標記過程仍然與“標記-清除”算法一樣,但后續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向內存空間一端移動,然后直接清理掉邊界以外的內存。
標記-整理算法主要用于老年代,移動存活對象是個極為負重的操作,而且這種操作需要 Stop The World 才能進行,只是從整體的吞吐量來考量,老年代使用標記-整理算法更加合適。