從萌新的角度理解JVM內(nèi)存管理

1. JVM內(nèi)存管理機(jī)制

在進(jìn)行Java程序設(shè)計(jì)時(shí),一般不涉及內(nèi)存的分配和內(nèi)存回收的相關(guān)代碼,此處引用一句話:Java和C++之間存在一堵由內(nèi)存動(dòng)態(tài)分配和垃圾收集技術(shù)所圍成的高墻,墻外的人想進(jìn)去,墻里面的人想出來,個(gè)人從這兩句話中,捕獲到了兩個(gè)點(diǎn)。

  • java的自動(dòng)內(nèi)存管理機(jī)制,極大的節(jié)省了開發(fā)人員的精力,避免了易錯(cuò)且復(fù)雜的內(nèi)存管理設(shè)計(jì),相對(duì)于手動(dòng)的內(nèi)存管理這是極大的飛躍。
  • java自動(dòng)內(nèi)存管理機(jī)制,其不能根據(jù)具體的場景提供最優(yōu)的內(nèi)存管理,其只提供普適的內(nèi)存管理機(jī)制。想比于C++的手動(dòng)內(nèi)存管理,靈活性不夠,存在制約系統(tǒng)性能的瓶頸。

第二點(diǎn)是我們深入了解JVM內(nèi)存管理機(jī)制的意義,通過對(duì)原理的把握,在指定的場景下設(shè)計(jì)JVM最優(yōu)的內(nèi)存管理策略,本文內(nèi)容組織結(jié)構(gòu)如下:

  • JVM內(nèi)存分配
  • JVM內(nèi)存回收

2. JVM內(nèi)存分配

ClassExample refereenceExample  = new ClassExample ();

上述代碼,如果粗略劃分的話,可以劃分為兩個(gè)過程:

  1. 在堆區(qū)分配對(duì)象內(nèi)存
  2. 將對(duì)象內(nèi)存地址賦值給對(duì)象引用
    上述兩個(gè)過程基本就是內(nèi)存分配中,比較重要的兩個(gè)知識(shí)點(diǎn)了,內(nèi)存分配策略和對(duì)象引用
2.1 堆區(qū)布局

對(duì)象分配在堆上這是毫無疑問的,如果在往下細(xì)分的話,那么堆區(qū)的內(nèi)存布局還是挺有講究的,大致可以分為如下布局:


堆內(nèi)存布局
  • 新生代:從名字可見一般,新生代區(qū)域中存放的對(duì)象一般是兩種:剛剛被new出來的對(duì)象,或者經(jīng)歷內(nèi)存回收次數(shù)不多的對(duì)象。
  • 老年代:老年代從名字也可見一般,一般存放兩種對(duì)象: 占用內(nèi)存比較多的大對(duì)象和經(jīng)歷過多次內(nèi)存回收過程的對(duì)象
  • 永久代:永久代的內(nèi)存就比較固定,每個(gè)類的class對(duì)象,一般存放在永久代當(dāng)中。

上述JVM堆區(qū)中的內(nèi)存布局代表的是邏輯視圖,并不是實(shí)際的物理布局,實(shí)際上,JVM了提供多種不同的內(nèi)存分配和回收的策略,每種策略抽閑出邏輯視圖都會(huì)有細(xì)微的差別,但是上述邏輯視圖可以說是所有邏輯視圖的根視圖

2.2 內(nèi)存分配一般過程

內(nèi)存分配的一般過程

在圖中有幾個(gè)重要的概念,需要著重強(qiáng)調(diào):

  • 對(duì)象大小閾值設(shè)定,在JVM中可以通過設(shè)PretenureSizeThreshold這個(gè)參數(shù),指定該閾值大小,如果待分配對(duì)象大小超過該閾值,嘗試在老年代中開辟內(nèi)存空間存儲(chǔ)對(duì)象,否則嘗試在新生代中開辟內(nèi)存空間存儲(chǔ)對(duì)象
  • MinorGC,這是JVM中一次內(nèi)存回收過程,內(nèi)存回收過程又稱垃圾回收過程,這次內(nèi)存回收是由新生代中內(nèi)存空間不足引起的,主要對(duì)新生代中的內(nèi)存對(duì)象進(jìn)行回收
  • FullGC,這也是JVM中的一次內(nèi)存回收過程,這次內(nèi)存回收過程是有老年代中內(nèi)存空間不足造成的,主要針對(duì)所有堆區(qū)中的內(nèi)存對(duì)象進(jìn)行回收,包括新生代,老年代,以及永久代
  • 對(duì)象年齡,經(jīng)過一次內(nèi)存回收后依然存活的對(duì)象,其年齡會(huì)加1。當(dāng)對(duì)象年齡超過一個(gè)指定閾值后,其由新生代轉(zhuǎn)向老年代存儲(chǔ)。這個(gè)對(duì)象年齡的閾值,同樣可以通過設(shè)置JVM的MaxTenuringThreshold參數(shù)進(jìn)行指定
2.3對(duì)象引用

JVM內(nèi)存區(qū)域的布局詳情中介紹了對(duì)象引用相關(guān)的內(nèi)容

3. JVM內(nèi)存回收

從程序員角度來看,內(nèi)存回收的過程是透明,具體細(xì)節(jié)都對(duì)程序員屏蔽了。JVM內(nèi)存區(qū)域的布局詳情,仔細(xì)的介紹了JVM中的內(nèi)存模型以及各個(gè)內(nèi)存區(qū)存儲(chǔ)的數(shù)據(jù)類型。

JVM內(nèi)存布局

內(nèi)存回收主要針對(duì)的內(nèi)存區(qū)域主要是堆區(qū)和方法區(qū),在上文中談及了MinorGC以及FullGC。MinorGC主要是針對(duì)堆區(qū)進(jìn)行內(nèi)存回收,F(xiàn)ullGC除了對(duì)堆區(qū)進(jìn)行回收,對(duì)方法區(qū)也進(jìn)行回收,是相對(duì)重量級(jí)的回收動(dòng)作。在內(nèi)存回收這個(gè)章節(jié)中,主要從以下三個(gè)方面闡述:
1. 什么樣的對(duì)象可回收?
解決方法:可達(dá)性分析
2. 如何回收內(nèi)存?
解決方法:標(biāo)記-清除算法,標(biāo)記-復(fù)制算法,標(biāo)記-整理算法等內(nèi)存回收算法
3.什么時(shí)候回收合適?
解決方法:安全點(diǎn)和安全區(qū)域

3.1 可達(dá)性分析

可達(dá)性分析示意圖
在上圖中,一共有七個(gè)對(duì)象,箭頭的走向代表引用關(guān)系、對(duì)象1引用對(duì)象2,對(duì)象2引用對(duì)象3。對(duì)象1是根節(jié)點(diǎn)(GCROOT),其他的是非根節(jié)點(diǎn)(GCROOT)。
可達(dá)性分析是指:不能被GCROOT節(jié)點(diǎn)通過引用鏈達(dá)到的節(jié)點(diǎn),將被列入擬回收對(duì)象范圍。對(duì)象6和對(duì)象7就不能被任何一個(gè)GCRoot節(jié)點(diǎn)所引用,是目標(biāo)回收對(duì)象。JVM認(rèn)為這些對(duì)象不具備使用價(jià)值,可以將其進(jìn)行內(nèi)存回收。
根節(jié)點(diǎn)(GCROOT)是上文中提出的重要概念,一般來說,如下對(duì)象可作為根節(jié)點(diǎn)對(duì)象:
(1)JVM虛擬機(jī)棧中引用的對(duì)象
(2)方法區(qū)中類靜態(tài)屬性引用對(duì)象
(3)方法區(qū)中類常量引用對(duì)象
(4)本機(jī)方法棧中引用對(duì)象
可達(dá)性分析是進(jìn)行內(nèi)存回收的判定條件,在可達(dá)性分析之后,確定哪些對(duì)象是回收目標(biāo)。除了可達(dá)性分析之外,引用計(jì)數(shù)法同樣可用來進(jìn)行判斷內(nèi)存回收目標(biāo)對(duì)象,但是其無法解決循環(huán)引用問題。主流的JVM都采用可達(dá)性分析。

3.2內(nèi)存回收算法

通過可達(dá)性分析之后,將確定哪些對(duì)象是回收目標(biāo),接著內(nèi)存回收算法將執(zhí)行具體的回收細(xì)節(jié)。下圖是內(nèi)存區(qū)域中三個(gè)狀態(tài),空閑內(nèi)存是未使用內(nèi)存,目標(biāo)回收內(nèi)存是可達(dá)性分析之后可回收內(nèi)存,已占內(nèi)存是不需要回收內(nèi)存。


內(nèi)存對(duì)象
  • 標(biāo)記-清除算法
    標(biāo)記-清除算法是比較直白的內(nèi)存回收算法,其直接釋放目標(biāo)回收內(nèi)存,沒有其他任何附加操作。該算法執(zhí)行后的結(jié)果如下:
    標(biāo)記-清除算法執(zhí)行后的內(nèi)存圖

    標(biāo)記-清除算法,邏輯簡單,易于理解,但是其有個(gè)致命的缺點(diǎn)就是其極易產(chǎn)生內(nèi)存碎片,在分配大對(duì)象時(shí),不能有足夠連續(xù)的內(nèi)存空間而造成頻繁的GC。
  • 標(biāo)記-復(fù)制算法
    標(biāo)記-復(fù)制算法是針對(duì)內(nèi)存碎片問題提出的一種算法。
    image.png

    在標(biāo)記-復(fù)制算法的設(shè)定中,始終有塊空白內(nèi)存區(qū)域,如上圖長內(nèi)存區(qū)域二。在內(nèi)存回收時(shí),將內(nèi)存區(qū)域一種存活對(duì)象全部按序復(fù)制到內(nèi)存區(qū)域二中后再對(duì)內(nèi)存區(qū)域一中所有對(duì)象進(jìn)行回收。通過這種做法可以極大的避免內(nèi)存碎片,但是空白內(nèi)存區(qū)域?qū)⑹冀K閑置,內(nèi)存利用率不高。在上文中提及將堆區(qū)的內(nèi)存分為兩個(gè)surivior區(qū)以及Eden區(qū)就是采用標(biāo)記-復(fù)制算法。Eden區(qū)和surivior之間大小比例分配默認(rèn)是8:1:1.
  • 標(biāo)記-整理算法
    標(biāo)記-整理算法,同樣是為解決內(nèi)存碎片提出的內(nèi)存回收算法,其在標(biāo)記-清楚算法的基礎(chǔ)上增加的整理功能,對(duì)內(nèi)存對(duì)象進(jìn)行拷貝移動(dòng),以保證空間內(nèi)存空間連續(xù)。
    標(biāo)記-整理算法回收后內(nèi)存

    標(biāo)記-整理算法,能夠克服內(nèi)存碎皮,且不需要額外內(nèi)存,但是其拷貝和移動(dòng)對(duì)象的時(shí)間消耗較高。該算法常用在老年代對(duì)象回收中。
3.3 什么時(shí)候回收內(nèi)存合適

通過可達(dá)性分析,可知哪些對(duì)象是需要回收的對(duì)象,在這過程中需要枚舉根節(jié)點(diǎn),這是一個(gè)耗時(shí)操作,為了保證內(nèi)存回收的順利進(jìn)行,必須保證引用關(guān)系的一致性,即在內(nèi)存回收過程中對(duì)象引用關(guān)系不會(huì)發(fā)生變化。只有在這些地方進(jìn)行內(nèi)存回收才是安全點(diǎn),因此JVM引入了安全點(diǎn)的概念,安全點(diǎn)處的對(duì)象引用關(guān)系不會(huì)改變,適合內(nèi)存回收。安全區(qū)域是對(duì)安全點(diǎn)概念的擴(kuò)充,指在這一段區(qū)域內(nèi)對(duì)象引用關(guān)系具有一致性,能進(jìn)行內(nèi)存回收。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容