您好,登錄后才能下訂單哦!
這篇文章主要講解了“JVM內(nèi)存分代、垃圾回收的概念是什么”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“JVM內(nèi)存分代、垃圾回收的概念是什么”吧!
都知道 JVM 的內(nèi)存區(qū)域分為5個部分,如果有疑惑,可以參看之前的一篇文章 - JVM 內(nèi)存區(qū)域介紹。
這里也簡單羅列一下 JVM 的五部分
程序計數(shù)器這是一塊較小的內(nèi)存空間,它的作用可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器,線程私有。
Java 虛擬機棧它是 Java方法執(zhí)行的內(nèi)存模型,每一個方法被調(diào)用到執(zhí)行完成的過程,就對應(yīng)著一個棧幀在虛擬機棧中從入棧到出棧的過程,線程私有。
本地方法棧跟虛擬機棧類似,不過本地方法棧用于執(zhí)行本地方法,線程私有。
Java 堆該區(qū)域存在的唯一目的就是存放對象,幾乎應(yīng)用中所有的對象實例都在這里分配內(nèi)存,所有線程共享。
方法區(qū)它用于存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù),所有線程共享。
都知道,任何一個應(yīng)用在啟動后,操作系統(tǒng)分配給它的內(nèi)存一定是有限的,所以如何合理有效的管理內(nèi)存,就變得尤為重要。
而從上節(jié)可知,我們一般討論的對象內(nèi)存分配均發(fā)生在 Java 堆上。所以這里說的內(nèi)存管理大部分情況下即指對 Java 堆內(nèi)存。而程序計數(shù)器、虛擬機棧他們隨著線程生而生,亡而亡,所以他們內(nèi)存相對比較好管理,出現(xiàn)的問題也比較少。
一個應(yīng)用啟動后,不停運行,不停的執(zhí)行命令,創(chuàng)建對象,而這些對象,大都存放在堆內(nèi)存區(qū)域。這部分區(qū)域的大小是有限的,而需要生成的對象是無限的,當(dāng)某一次創(chuàng)建對象時發(fā)現(xiàn)堆內(nèi)存實在沒有空間可用來創(chuàng)建對象的時候,JVM 就會爆出 OutOfMemoryError 異常(后文統(tǒng)稱 OOM),程序就會掛掉。
上面只是說明了一下表象。其實 OOM 遠不是上面說的那么簡單。如果要理解 OOM,這里還有一些其他知識需要說明。
OOM 發(fā)生前其實 JVM 會進行內(nèi)存的垃圾回收(GC)。
垃圾回收有多種不同的實現(xiàn)算法。
為了更好的管理內(nèi)存,堆內(nèi)存進行了分代。
堆內(nèi)存的新生代和老年代的垃圾回收算法不一致。
其實,這里的知識需要綜合理解,你才會對 OOM 有一個全面的認識。
一個應(yīng)用啟動,操作系統(tǒng)會給他分配一個初始的內(nèi)存大小,由上可知,這部分內(nèi)存大部分應(yīng)該屬于堆內(nèi)存,JVM 為了更好地利用管理這部分內(nèi)存,對該區(qū)域做了劃分。一部分成為新生代,另一部分稱為老年代。
一開始對象的創(chuàng)建都發(fā)生在新生代,隨著對象的不斷創(chuàng)建,如果新生代沒有空間創(chuàng)建新對象,將會發(fā)生 GC ,這時的 GC 稱之為 Minor GC,位于新生代的對象每經(jīng)過一次 Minor GC 后,如果這個對象沒有被回收,則為自己的標(biāo)記數(shù)加1,這個標(biāo)記數(shù)用于標(biāo)識這個對象經(jīng)歷了多少次的 Minor GC,對于 Sun 的 Hotspot 虛擬機,如果這個次數(shù)超過 15 ,該對象才會被移動到老年代。
隨著時間的推移,如果老年代也沒有足夠的空間容納對象,老年代也會試著發(fā)起 GC,這時的 GC 被稱為 Full GC。
相比 Minor GC,F(xiàn)ull GC 發(fā)生的次數(shù)比較少,但是每發(fā)生一次 Full GC,整個堆內(nèi)存區(qū)域都需要執(zhí)行一次垃圾回收,這對程序性能造成的影響比 Minor GC 大很多。所以我們應(yīng)該盡量避免或者減少 Full GC 的發(fā)生。
同時,在堆內(nèi)存區(qū)域,發(fā)生最多的 GC 情形就是新生代的 Minor GC 了,因為所有的對象會優(yōu)先去新生代開辟空間,所以這塊的內(nèi)存變化會很快,只有內(nèi)存不夠用,就會發(fā)生 GC,但是一般的 Minor GC 執(zhí)行比 Full GC 快很多。為什么呢?因為新生代和老年代的垃圾回收算法不一樣。
這是最基礎(chǔ)的收集算法,如它的名字一樣,算法分為“標(biāo)記”和“清除”兩個階段:
首先標(biāo)記出所有需要回收的對象,在標(biāo)記完成后統(tǒng)一回收掉所有被標(biāo)記的對象。
之所以說它是最基礎(chǔ)的收集算法,是因為后續(xù)的收集算法都是基于這種思路并對其缺點進行改進而得到的。
它的主要缺點有兩個:一個是效率問題,標(biāo)記和清除過程的效率都不高;另外一個是空間問題,標(biāo)記清除之后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會導(dǎo)致,當(dāng)程序在以后的運行過程中需要分配較大對象時無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動作。
為了解決效率問題,一種稱為“復(fù)制”(Copying)的收集算法出現(xiàn)了,它將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對象復(fù)制到另外一塊上面,然后再把已使用過的內(nèi)存空間一次清理掉。
這樣使得每次都是對其中的一塊進行內(nèi)存回收,內(nèi)存分配時也就不用考慮內(nèi)存碎片等復(fù)雜情況,只要移動堆頂指針,按順序分配內(nèi)存即可,實現(xiàn)簡單,運行高效。只是這種算法的代價是將內(nèi)存縮小為原來的一半,未免太高了一點。
但是這種算法的效率相當(dāng)高,所以,現(xiàn)在的商業(yè)虛擬機都采用這種收集算法來回收新生代。為什么新生代可以使用復(fù)制算法呢?
IBM 有專門研究表明,新生代中的對象 98% 都是朝生夕死,所以就不需要按照1:1的比例來劃分內(nèi)存空間。這里鑒于此,新生代采用了如下的劃分策略。
現(xiàn)在把新生代再劃分為三部分,一塊較大的 Eden(伊甸園) 和兩塊較小的 Survivor(幸存者) 區(qū)域。
當(dāng)回收時,將 Eden 和 Survivor 中還存活著的對象一次性地拷貝到另外一塊Survivor空間上,最后清理掉Eden和剛才用過的Survivor的空間。HotSpot 虛擬機默認Eden和Survivor的大小比例是8∶1,也就是每次新生代中可用內(nèi)存空間為整個新生代容量的90%(80%+10%),只有10%的內(nèi)存是會被“浪費”的。
這樣清理完成后,原來的 Survivor 就空了,并一直保持為空,直到下次 Minor GC 時,它再作為存活對象的盛放地。兩個 Survivor 就這樣輪流當(dāng)做 GC 過程中新生代存活對象的中轉(zhuǎn)站。
但是,如果使用復(fù)制算法的內(nèi)存區(qū)域有大量的存活對象時,復(fù)制算法就會變得捉襟見肘,這時需要更大的 Survivor 區(qū)用于盛放那些存活對象,甚至可能需要 1:1的比例。所以針對堆內(nèi)存區(qū)域的老年代,就有了下面的算法。
標(biāo)記過程仍然與“標(biāo)記-清除”算法一樣,但后續(xù)步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存
。這種方法避免了碎片的產(chǎn)生,同時也不需要一塊額外的內(nèi)存空間,對于老年代會比較合適。
但是相比復(fù)制算法,雖然該算法占用的內(nèi)存空間少,但是耗費的垃圾回收時間會比復(fù)制算法久,所以上面也說了
我們應(yīng)該盡量避免或者減少 Full GC 的發(fā)生。
這兩種算法用精煉的語言描述就是
復(fù)制算法:用空間換時間
標(biāo)記-整理算法:用時間換空間
一句話 魚與熊掌不可兼得,但是針對新生代和老年代,他們都是最佳的選擇。
感謝各位的閱讀,以上就是“JVM內(nèi)存分代、垃圾回收的概念是什么”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對JVM內(nèi)存分代、垃圾回收的概念是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。