jvm垃圾回收算法詳解,看這一篇就夠了(求點贊)


前言(求點贊)

我們今天先聊聊jvm的垃圾回收算法,大家先了解垃圾算法有哪些,在去學習有哪些垃圾回收器,然后我們在學習如何對jvm進行參數調優

垃圾回收(Garbage Collection,GC),顧名思義就是釋放垃圾占用的空間,防止內存泄露。有效的使用可以使用的內存,對內存堆中已經死亡的或者長時間沒有使用的對象進行清除和回收。

如何找到程序里的垃圾

引用計數法

給每個對象添加一個計數器RC,當有地方引用該對象時計數器加1,當引用失效時計數器減1。用對象計數器是否為0來判斷對象是否可被回收。缺點:無法解決循環引用的問題。

先創建一個字符串,String m = new String("jack");,這時候 "jack" 有一個引用,就是m。然后將m設置為null,這時候 "jack" 的引用次數就等於 0 了,在引用計數算法中,意味着這塊內容就需要被回收了。

引用計數算法是將垃圾回收分攤到整個應用程序的運行當中了,而不是在進行垃圾收集時,要掛起整個應用的運行,直到對堆中所有對象的處理都結束。因此,采用引用計數的垃圾收集不屬於嚴格意義上的Stop-The-World的垃圾收集機制。

看似很美好,但我們知道JVM的垃圾回收就是Stop-The-World的,那是什么原因導致我們最終放棄引用計數算法呢?看下面的例子。

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和b互相引用了 a.instance = b; b.instance = a; a = null; b = null; } } 復制代碼

我們可以看到,最后這2個對象已經不可能再被訪問了,但由於他們相互引用着對方,導致它們的引用計數永遠都不會為0,通過引用計數算法,也就永遠無法通知GC收集器回收它們。

可達性分析算法

通過GC ROOT的對象作為搜索起始點,通過引用向下搜索,所走過的路徑稱為引用鏈。通過對象是否有到達引用鏈的路徑來判斷對象是否可被回收(可作為GC ROOT的對象:虛擬機棧中引用的對象,方法區中類靜態屬性引用的對象,方法區中常量引用的對象,本地方法棧中JNI引用的對象)

通過可達性算法,成功解決了引用計數所無法解決的循環依賴問題,只要你無法與GC Root建立直接或間接的連接,系統就會判定你為可回收對象。那這樣就引申出了另一個問題,哪些屬於GC Root

Java內存區域中可以作為GC ROOT的對象:

虛擬機棧中引用的對象

public class StackLocalParameter { public StackLocalParameter(String name) {} public static void testGC() { StackLocalParameter s = new StackLocalParameter("localParameter"); s = null; } } 復制代碼

此時的s,即為GC Root,當s置空時,localParameter對象也斷掉了與GC Root的引用鏈,將被回收。


方法區中類靜態屬性引用的對象

public class MethodAreaStaicProperties { public static MethodAreaStaicProperties m; public MethodAreaStaicProperties(String name) {} public static void testGC(){ MethodAreaStaicProperties s = new MethodAreaStaicProperties("properties"); s.m = new MethodAreaStaicProperties("parameter"); s = null; } } 復制代碼

此時的s,即為GC Root,s置為null,經過GC后,s所指向的properties對象由於無法與GC Root建立關系被回收。而m作為類的靜態屬性,也屬於GC Root,parameter 對象依然與GC root建立着連接,所以此時parameter對象並不會被回收。


方法區中常量引用的對象

public class MethodAreaStaicProperties { public static final MethodAreaStaicProperties m = MethodAreaStaicProperties("final"); public MethodAreaStaicProperties(String name) {} public static void testGC() { MethodAreaStaicProperties s = new MethodAreaStaicProperties("staticProperties"); s = null; } } 復制代碼

m即為方法區中的常量引用,也為GC Root,s置為null后,final對象也不會因沒有與GC Root建立聯系而被回收。


本地方法棧中引用的對象

任何native接口都會使用某種本地方法棧,實現的本地方法接口是使用C連接模型的話,那么它的本地方法棧就是C棧。當線程調用Java方法時,虛擬機會創建一個新的棧幀並壓入Java棧。然而當它調用的是本地方法時,虛擬機會保持Java棧不變,不再在線程的Java棧中壓入新的幀,虛擬機只是簡單地動態連接並直接調用指定的本地方法。

垃圾回收算法

在確定了哪些垃圾可以被回收后,垃圾收集器要做的事情就是開始進行垃圾回收,但是這里面涉及到一個問題是:如何高效地進行垃圾回收。這里我們討論幾種常見的垃圾收集算法的核心思想。

標記-清除算法

標記清除算法(Mark-Sweep)是最基礎的一種垃圾回收算法,它分為2部分,先把內存區域中的這些對象進行標記,哪些屬於可回收標記出來,然后把這些垃圾拎出來清理掉。就像上圖一樣,清理掉的垃圾就變成未使用的內存區域,等待被再次使用。但它存在一個很大的問題,那就是內存碎片。

上圖中等方塊的假設是2M,小一些的是1M,大一些的是4M。等我們回收完,內存就會切成了很多段。我們知道開辟內存空間時,需要的是連續的內存區域,這時候我們需要一個2M的內存區域,其中有2個1M是沒法用的。這樣就導致,其實我們本身還有這么多的內存的,但卻用不了。

復制算法

復制算法(Copying)是在標記清除算法基礎上演化而來,解決標記清除算法的內存碎片問題。它將可用內存按容量划分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象復制到另外一塊上面,然后再把已使用過的內存空間一次清理掉。保證了內存的連續可用,內存分配時也就不用考慮內存碎片等復雜情況。復制算法暴露了另一個問題,例如硬盤本來有500G,但卻只能用200G,代價實在太高。

標記-壓縮算法

標記-壓縮算法標記過程仍然與標記-清除算法一樣,但后續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,再清理掉端邊界以外的內存區域。

標記壓縮算法解決了內存碎片的問題,也規避了復制算法只能利用一半內存區域的弊端。標記壓縮算法對內存變動更頻繁,需要整理所有存活對象的引用地址,在效率上比復制算法要差很多。一般是把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點采用最適當的收集算法。

分代收集算法

分代收集算法分代收集算法嚴格來說並不是一種思想或理論,而是融合上述3種基礎的算法思想,而產生的針對不同情況所采用不同算法的一套組合拳,根據對象存活周期的不同將內存划分為幾塊。

在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用復制算法,只需要付出少量存活對象的復制成本就可以完成收集。

在老年代中,因為對象存活率高、沒有額外空間對它進行分配擔保,就必須使用標記-清理算法或者標記-整理算法來進行回收。

之后講jvm有哪些垃圾回收器

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM