内存清除算法
1.標記-清除:
標記 GC roots可達的對象,清理掉沒有被標記的對象。
做法:當堆中的有效內存空間被耗盡的時候,就會停止整個程序,然后進行兩項工作,第一項是標記,第二項是清除。
? ? 1.標記:遍歷所有的GC Roots,然后將GC Roots可達的對象標記為存活的對象。
? ? 2.清除:清除的過程將遍歷對中所有的對象,將沒有標記的對象全部清除掉。
當程序運行期間,若可以使用的內存被耗盡的時候,GC線程就會被觸發并將程序暫停,隨后將依舊存活的對象標記一遍,最終再將堆中所有沒被標記的對象全部清除掉,接下來便讓程序恢復運行。
標記的時候為什么有停止程序運行呢?
假設我們剛標記完圖中最右邊的那個對象,暫且記為A,結果此時在程序當中又new了一個新對象B,且A對象可以到達B對象。但是由于此時A對象已經標記結束,B對象此時的標記位依然是0,因為它錯過了標記階段。因此當接下來輪到清除階段的時候,新對象B將會被苦逼的清除掉。如此一來,不難想象結果,GC線程將會導致程序無法正常工作。上面的結果當然令人無法接受,我們剛new了一個對象,結果經過一次GC,忽然變成null了,這還怎么玩?
缺點:
? ? ?1.效率較低(遞歸,遍歷整個堆的對象)而且在進行GC的時候,需要停止應用程序,這會導致用戶體驗非常差勁。
? ? ?2.清理出來的內存空間不是連續的(死亡對象都是隨機出現在內存的各個角落的)。再分配數組對象的時候,尋找連續的內存空間不太好找。
2.復制算法:
將內存分為兩塊
?
做法:當內存空間耗盡時,暫停程序運行,開啟復制算法GC線程。將活動區間(內存)的存活對象復制到空閑的那一塊內存區域,并且嚴格的按照內存地址依次排列,與此同時GC線程將更新存活對象的內存引用地址指向新的內存地址。 將標記為死亡對象一次清除掉。之后(活動)的那一塊內存區域變為空閑,空閑的變為忙。
缺點:
? ? ?1.浪費了一半內存。
? ? ?2.如果對象的存活率很高,假設為100%存活,那么就需要將所有對象都復制一遍,并且將所有引用地址復制一遍。復制這一工作所話費的時間,在對象存活率達到一定程度時,(復制所用時間)將會變的不可忽視。
總結:要想使用復制算法,最起碼對象的存活率要非常低才行,而且最重要的是,我們必須要克服50%的內存浪費。
?
3.標記-整理算法:
做法:
? ? ?1.和標記-清除算法一樣,標記出存活的對象。
? ? ?2.按照內存地址依次排列,而未被標記的內存會被清理掉。
不難看出,標記-整理算法不僅可以彌補 標記-清除 算法中內存區要分散的缺點,也消除了復制算法中內存減半的高額代價。(但是從效率上講,標記-整理算法要低于復制算法)
算法總結:
1.都是基于根搜索算法(GC Roots)的來判斷一個對象是否應該被回收
2.在GC線程開啟時,或者說GC過程開始時,它們都要暫停服務。
3. 效率:
? ? ?復制算法 ? > ? 標記/整理算法 ? > ? 標記/清除算法(此處的效率只是簡單的對比時間復雜度,實際情況不一定如此)。
? ? ?內存整齊度:復制算法 ? = ? 標記/整理算法 ? > ? 標記/清除算法。
? ? ?內存利用率:標記/整理算法 ? = ? 標記/清除算法 ? > ? 復制算法。
?
結束語
到此我們已經將三個算法了解清楚了,可以看出,效率上來說,復制算法是當之無愧的老大,但是卻浪費了太多內存,而為了盡量兼顧上面所提到的三個指標,標記/整理算法相對來說更平滑一些,但效率上依然不盡如人意,它比復制算法多了一個標記的階段,又比標記/清除多了一個整理內存的過程。
難道就沒有一種最優算法嗎?
當然是沒有的,這個世界是公平的,任何東西都有兩面性,試想一下,你怎么可能找到一個又漂亮又勤快又有錢又通情達理,性格又合適,家境也合適,身高長相等等等等都合適的女人?就算你找到了,至少有一點這個女人也肯定不滿足,那就是多半不會恰巧又愛上了與LZ相似的各位苦逼猿友們。你是不是想說你比LZ強太多了,那LZ只想對你說,高富帥是不會爬在電腦前看技術文章的,0.0。
但是古人就是給力,古人說了,找媳婦不一定要找最好的,而是要找最合適的,聽完這句話,瞬間感覺世界美好了許多。
算法也是一樣的,沒有最好的算法,只有最合適的算法。
既然這三種算法都各有缺陷,高人們自然不會容許這種情況發生。因此,高人們提出可以根據對象的不同特性,使用不同的算法處理,類似于蘿卜白菜各有所愛的原理。于是奇跡發生了,高人們終于找到了GC算法中的神級算法-----分代搜集算法。
?
4.!分代搜集算法:
本質?屬于前三種算法的實際應用 新生代,老年代,永久代
?
新生代:朝生夕滅,存活時間短。eg:某一個方法的局部變量,循環內的臨時變量等等。
老年代:生存時間長,但總會死亡。eg:緩存對象,數據庫連接對象,單例對象等等。
永久代:幾乎一直不滅。eg:String池中的對象,加載過的類信息。
?
java堆:新生代,老年代 方法區(永久代):
?
使用這樣的方式,我們只浪費了10%的內存,這個是可以接受的,因為我們換來了內存的整齊排列與GC速度。第二點是,這個策略的前提是,每次存活的對象占用的內存不能超過這10%的大小,一旦超過,多出的對象將無法復制。
為了解決上面的意外情況,也就是存活對象占用的內存太大時的情況,高手們將JAVA堆分成兩部分來處理,上述三個區域則是第一部分,稱為新生代或者年輕代。而余下的一部分,專門存放老不死對象的則稱為年老代。
JVM在進行GC時,并非每次都對上面三個區域一起回收,大部分回收的是新生代。因此GC按照回收的區域又分為兩種:普通GC,全局GC
普通GC:只針對新生代區域的GC
全局GC:針對老年代的GC,偶爾伴隨新生代的GC以及對永久帶的GC
由于年老代與永久代相對來說GC效果不好,而且二者的內存使用增長速度也慢,因此一般情況下,需要經過好幾次普通GC,才會觸發一次全局GC。
轉載于:https://www.cnblogs.com/tian666/p/7908750.html
總結
- 上一篇: PHP命令空间namespace及use
- 下一篇: fiddler抓取https请求