理解Java內(nèi)存區(qū)域與垃圾收集器

本文主要介紹java內(nèi)存區(qū)域和GC回收

  • java內(nèi)存區(qū)域
  • 垃圾收集器
  • 參考

java內(nèi)存區(qū)域

運(yùn)行時(shí)內(nèi)存區(qū)域

java虛擬機(jī)在執(zhí)行java程序的過程中會(huì)把它所管理的內(nèi)存劃分為若干個(gè)不同的數(shù)據(jù)區(qū)域。


java內(nèi)存區(qū)域

我們注意到運(yùn)行時(shí)區(qū)域主要會(huì)包括5部分區(qū)域,它們有個(gè)各自的用途,以及創(chuàng)建和銷毀時(shí)間,有的依賴虛擬機(jī)進(jìn)程,有的依賴用戶線程。

  • 程序計(jì)數(shù)器
    程序計(jì)數(shù)器是一塊較小的內(nèi)存空間,它的作用是當(dāng)前線程所執(zhí)行到的字節(jié)碼的位置指示器。字節(jié)碼解釋器工作時(shí)就是通過改變計(jì)數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,從而達(dá)到分支、循環(huán)、跳轉(zhuǎn)、異常處理等基本功能。
    java虛擬機(jī)中的多線程實(shí)際是通過線程輪流切換實(shí)現(xiàn)的。所以實(shí)際上在同一時(shí)刻,處理器的一個(gè)內(nèi)核只會(huì)執(zhí)行一條指令。因此為了線程切換后還能恢復(fù)到正確的執(zhí)行位置,需要每個(gè)線程都要有一個(gè)獨(dú)立的程序計(jì)數(shù)器。而且他們之間互補(bǔ)影響,獨(dú)立工作。所以程序計(jì)數(shù)器是一塊線程私有的內(nèi)存。
  • 本地方法棧
    與程序計(jì)數(shù)器一樣,本地方法棧也是線程私有的。本地方法棧為虛擬機(jī)提供使用Native方法服務(wù)。由于虛擬機(jī)規(guī)范并沒有對(duì)本地方法棧中的使用語言和數(shù)據(jù)結(jié)構(gòu)等做強(qiáng)制規(guī)定,所以虛擬機(jī)可以自由實(shí)現(xiàn)。
  • java虛擬機(jī)棧
    同本地方法棧,虛擬機(jī)棧是線程私有,它的生命周期與當(dāng)前線程相同。它為虛擬機(jī)執(zhí)行java方法提供服務(wù)。它描述的內(nèi)存模型:每個(gè)方法被執(zhí)行的時(shí)候會(huì)同時(shí)創(chuàng)建一個(gè)棧楨,用于存儲(chǔ)局部變量、操作棧、動(dòng)態(tài)鏈接、方法出口等信息。每個(gè)方法的調(diào)用到返回結(jié)果的過程,就是對(duì)應(yīng)一個(gè)棧楨的入棧與出棧。
    經(jīng)常有人會(huì)說java內(nèi)存可以粗糙的區(qū)分為堆和棧,這里的棧就是虛擬機(jī)棧,而虛擬機(jī)棧中最重要的就是局部變量表。
    局部變量表存放了編譯期可知的基本數(shù)據(jù)類型、對(duì)象的引用(reference類型,它可能只想對(duì)象起始地址的引用指針,也可能指向代表改對(duì)象的句柄)。局部變量表所需要的內(nèi)存在編譯期完成分配,當(dāng)進(jìn)入一個(gè)方法時(shí),此方法所需要的內(nèi)存空間大小是確定的,所以在方法運(yùn)行期間,不會(huì)改變局部變量表的大小。
  • java堆
    對(duì)于虛擬機(jī)來說,堆是其所管理的最大的一塊內(nèi)存。java堆是指被線程共享的一塊內(nèi)存區(qū)域,它在虛擬機(jī)啟動(dòng)時(shí)即創(chuàng)建,堆的唯一目的時(shí)存放對(duì)象實(shí)例。同時(shí)由于堆空間有限,對(duì)象的創(chuàng)建和銷毀是時(shí)常發(fā)生的,所以java堆是垃圾收集器的主要管理區(qū)域,所以java堆有時(shí)也會(huì)稱為GC堆。現(xiàn)在的GC回收基本都采用分代回收算法,所以堆可以細(xì)分為新生代和老年代,新生代又可以分為eden區(qū),from Survivor空間和to Survivor空間等。對(duì)于堆中的各個(gè)區(qū)域分配和回收細(xì)節(jié),在GC部分講解。
    在虛擬機(jī)規(guī)范中,沒有強(qiáng)制要求堆是物理內(nèi)存連續(xù)的,只是邏輯上連續(xù)即可。所以當(dāng)前的主流虛擬機(jī)的堆空間都是可以動(dòng)態(tài)擴(kuò)容的,可以通過-Xmx和-Xms控制。
  • 方法區(qū)
    方法區(qū)同java堆都是線程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。java虛擬機(jī)實(shí)現(xiàn)規(guī)范對(duì)該區(qū)域并沒有強(qiáng)制要求實(shí)現(xiàn)GC回收,所以相對(duì)而言,該區(qū)域的垃圾收集器很少出現(xiàn),所以有人開發(fā)者會(huì)成稱該區(qū)域?yàn)橛谰么_@個(gè)區(qū)域的內(nèi)存回收主要是針對(duì)常量池的回收和對(duì)類型的卸載。
    運(yùn)行時(shí)常量池
    一個(gè)class文件除了有類的版本、字段、方法、接口等描述以外,還有一項(xiàng)是常量池,用于存放編譯期間生成的各種字面量和符號(hào)引用,這部分內(nèi)容將在類加載后存放到方法區(qū)的運(yùn)行時(shí)常量池中。運(yùn)行時(shí)常量池是具有動(dòng)態(tài)性的,java虛擬機(jī)對(duì)class文件的每一部分的格式有嚴(yán)格的規(guī)定,每個(gè)字節(jié)用于存儲(chǔ)哪種數(shù)據(jù)都有規(guī)范要求,這樣才會(huì)被虛擬機(jī)認(rèn)可。但是對(duì)于常量池是比較寬松的,因?yàn)閖ava并不要求常量一定要編譯期產(chǎn)生,也可以在運(yùn)行期間放入常量,比如String的intern()方法。

對(duì)象訪問

在java虛擬機(jī)棧中我們提到局部變量表存放了對(duì)象的引用,我們都知道對(duì)象是分配的java堆中的,那么具體是怎么引用的呢?
比如Object obj = new Object();,假設(shè)這句代碼出現(xiàn)在方法體中,那么“Object obj ”這部分語義將會(huì)反映到j(luò)ava棧的本地變量表中(為reference類型),而“new Object()”這部分語義將會(huì)反映在java堆上,形成一塊存儲(chǔ)了Object類型所有實(shí)例數(shù)據(jù)值的結(jié)構(gòu)化內(nèi)存。
由于reference類型在java虛擬機(jī)規(guī)范中只規(guī)定了一個(gè)指向?qū)ο蟮囊茫栽趯?shí)際虛擬機(jī)中訪問會(huì)有所不同,主流訪問有兩種:

  • 句柄訪問
  • 直接指針訪問
句柄訪問

java堆會(huì)劃分出一小塊內(nèi)存空間作為句柄池,reference中存儲(chǔ)的就是對(duì)象的句柄地址,二句柄中包含了對(duì)象實(shí)例數(shù)據(jù)和類型數(shù)據(jù)的各自地址信息。


image.png
直接地址訪問

reference中直接存儲(chǔ)的就是對(duì)象的地址,java堆需要考慮對(duì)象的布局中如何存放訪問類型數(shù)據(jù)的相關(guān)信息。


image.png

這兩種對(duì)象的訪問方式各有優(yōu)勢(shì),使用句柄訪問方式的最大好處就是reference中存儲(chǔ)的是穩(wěn)定的句柄地址,在對(duì)象被移動(dòng)時(shí)只會(huì)改變句柄中的實(shí)例數(shù)據(jù)指針,而reference本身不需要被修改。使用直接指針訪問方式的最大好處就是速度更快,它節(jié)省了一次指針定位的的時(shí)間開銷。

垃圾收集器

判斷對(duì)象死亡

GC在對(duì)堆內(nèi)存進(jìn)行回收前,第一件事是需要確定哪些對(duì)象是需要被回收的,所以就需要判斷對(duì)象是否存活。一般的有兩種方法來判斷:

  1. 引用計(jì)數(shù)法
    給一個(gè)對(duì)象添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有地方對(duì)其引用時(shí),計(jì)數(shù)器加1,當(dāng)引用實(shí)效時(shí),計(jì)數(shù)器減1,任何時(shí)刻計(jì)數(shù)器為0時(shí)就表示該對(duì)象不再被使用。
    引用計(jì)數(shù)法實(shí)現(xiàn)簡(jiǎn)單,通常是比較高效的,但是引用計(jì)數(shù)法有個(gè)弊端是當(dāng)兩個(gè)不再被使用的對(duì)象互相引用時(shí),導(dǎo)致兩者都不會(huì)被釋放。
  2. 根搜索算法
    根搜索算法是指通過一系列名為“GC roots"的對(duì)象為起點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,搜索過的路徑稱為引用鏈,當(dāng)一個(gè)對(duì)象到GC roots沒有任何引用鏈相連,就表示此對(duì)象不再被使用。
    在java語言中,作為GC roots的對(duì)象包括以下幾種:
    a. java虛擬機(jī)棧(棧楨中本地變量表)中引用的對(duì)象
    b. 方法區(qū)中類靜態(tài)屬性引用的對(duì)象
    c. 方法區(qū)中常量引用的對(duì)象
    d. 本地方法棧中JNI引用的對(duì)象

方法區(qū)回收

前面已經(jīng)提到方法區(qū)是很少出現(xiàn)垃圾收集器的,因?yàn)榉椒▍^(qū)回收的性價(jià)比比較低,通常堆內(nèi)存的回收一次可以回收70%-95%的空間,但方法區(qū)的垃圾收集器效率很低。
一般的,方法區(qū)回收主要由兩部分:
1.廢棄常量
廢棄的常量與堆回收比較類似,只需要指導(dǎo)該常量是否在其他地方被使用即可。
2.無用的類
這種情況的判斷比較苛刻,一般要求滿足以下三個(gè)條件才算是無用的:
a. 該類的所有實(shí)例都被回收
b. 加載該類的ClassLoader也被回收
c. 該類對(duì)應(yīng)的java.lang.class對(duì)象沒有在任何地方被引用,無法在任何地方通過反射訪問該類

GC回收算法

1.標(biāo)記-清除算法
最基礎(chǔ)的收集算法是“標(biāo)記-清除”(Mark-Sweep)算法,如同它的名字一樣,算法分為“標(biāo)記”和“清除”兩個(gè)階段。
a. 首先標(biāo)記出所有需要回收的對(duì)象
b. 在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象。
缺點(diǎn):
效率問題:標(biāo)記和清除兩個(gè)過程的效率都不高
空間問題:標(biāo)記清除之后產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會(huì)導(dǎo)致以后程序運(yùn)行過程中需要分配較大對(duì)象時(shí),無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動(dòng)作。


標(biāo)記-清除算法

2.復(fù)制算法
目的是為了解決效率問題。
將可用內(nèi)存按容量大小劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)一塊內(nèi)存使用完了,就將還存活著的對(duì)象復(fù)制到另一塊上面,然后再把已使用過的內(nèi)存空間一次清理掉。這樣使得每次都是對(duì)整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收,內(nèi)存分配時(shí)也就不用考慮內(nèi)存碎片等復(fù)雜情況。
缺點(diǎn): 將內(nèi)存縮小為了原來的一半。


現(xiàn)代的商業(yè)虛擬機(jī)都采用這種收集算法來回收新生代,IBM公司的專門研究表明,新生代中對(duì)象98%對(duì)象是“朝生夕死”的,所以不需要按照1:1的比例來劃分內(nèi)存空間,而是將內(nèi)存分為較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。

3.標(biāo)記-整理算法
復(fù)制收集算法在對(duì)象存活率較高時(shí),就要進(jìn)行較多的復(fù)制操作,效率就會(huì)變低。 根據(jù)老年代的特點(diǎn),提出了“標(biāo)記-整理”算法。
標(biāo)記過程仍然與”標(biāo)記-清除“算法一樣,但后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行清理,而是讓所有存活的對(duì)象都向一端移動(dòng),然后直接清理掉邊界以外的內(nèi)存。

4.分代收集算法
一般是把Java堆分為新生代和老年代,這樣就可以根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴āT谛律校看卫占瘯r(shí)都發(fā)現(xiàn)有大批對(duì)象死去,只有少量存活,那就選用復(fù)制算法。在老年代中,因?yàn)閷?duì)象存活率高、沒有額外空間對(duì)它進(jìn)行分配擔(dān)保,就必須采用“標(biāo)記-清除”或“標(biāo)記-整理”算法來進(jìn)行回收。JVM把年輕代分為了三部分:1個(gè)Eden區(qū)和2個(gè)Survivor區(qū)(分別叫from和to),默認(rèn)比例為8:1。
工作過程:一般情況下,新創(chuàng)建的對(duì)象都會(huì)被分配到Eden區(qū)(一些大對(duì)象特殊處理),這些對(duì)象經(jīng)過第一次GC后,如果仍然存活,將會(huì)被移到Survivor區(qū)。對(duì)象在Survivor區(qū)中每熬過一次GC,年齡就會(huì)增加1歲,當(dāng)它的年齡增加到一定程度時(shí),就會(huì)被移動(dòng)到年老代中。 因?yàn)槟贻p代中的對(duì)象基本都是朝生夕死的(80%以上),所以在年輕代的垃圾回收算法使用的是復(fù)制算法,復(fù)制算法不會(huì)產(chǎn)生內(nèi)存碎片。在GC開始的時(shí)候,對(duì)象只會(huì)存在于Eden區(qū)和名為“From”的Survivor區(qū),Survivor區(qū)“To”是空的。緊接著進(jìn)行GC,Eden區(qū)中所有存活的對(duì)象都會(huì)被復(fù)制到“To”,而在“From”區(qū)中,仍存活的對(duì)象會(huì)根據(jù)他們的年齡值來決定去向。年齡達(dá)到一定值(年齡閾值,可以通過-XX:MaxTenuringThreshold來設(shè)置)的對(duì)象會(huì)被移動(dòng)到年老代中,沒有達(dá)到閾值的對(duì)象會(huì)被復(fù)制到“To”區(qū)域。經(jīng)過這次GC后,Eden區(qū)和From區(qū)已經(jīng)被清空。這個(gè)時(shí)候,“From”和“To”會(huì)交換他們的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎樣,都會(huì)保證名為To的Survivor區(qū)域是空的。GC會(huì)一直重復(fù)這樣的過程,直到“To”區(qū)被填滿,“To”區(qū)被填滿之后,會(huì)將所有對(duì)象移動(dòng)到年老代中。

空間分配擔(dān)保

先了解下Minor GC與Major GC/Full GC

  • Minor GC
    即新生代GC,指發(fā)生在新生代的垃圾收集動(dòng)作,Minor GC的回收的對(duì)象大多具備朝生夕滅的特性,所以Minor GC是非常頻繁,并且回收速度比較快。
  • Major GC/Full GC
    即老年代GC,指發(fā)生在老年代的垃圾收集動(dòng)作,出現(xiàn)Major GC,經(jīng)常會(huì)伴隨至少一次的Minor GC。Major GC的速度一般比Minor GC慢10倍以上。

在發(fā)生Minor GC時(shí),虛擬機(jī)會(huì)檢測(cè)之前每次晉升到老年代的平均大小是否大于老年代的剩余空間大小,如果大于,則改為直接進(jìn)行一次Full GC,如果小于,則查看HandlePromotionFailure設(shè)置是否允許擔(dān)保失敗,如果允許,那么只會(huì)進(jìn)行Minor GC,如果不允許,那么進(jìn)行一次Full GC。
在分代回收算法中提到過,新生代使用復(fù)制收集算法,但為了內(nèi)存利用率,只使用其中一個(gè)Survivor空間來作為輪換備份,因此當(dāng)出現(xiàn)大量對(duì)象在Minor GC后仍然存活的情況(最極端的情況就是內(nèi)存回收后新生代中所有對(duì)象都存活),就需要老年代進(jìn)行分配擔(dān)保,把Survivor無法容納的對(duì)象直接進(jìn)入老年代。與生活中的貸款擔(dān)保類似,老年代要進(jìn)行這樣的擔(dān)保,前提是老年代本身還有容納這些對(duì)象的剩余空間,一共有多少對(duì)象會(huì)活下來在實(shí)際完成內(nèi)存回收之前是無法明確知道的,所以只好取之前每一次回收晉升到老年代對(duì)象容量的平均大小值作為經(jīng)驗(yàn)值,與老年代的剩余空間進(jìn)行比較,決定是否進(jìn)行Full GC來讓老年代騰出更多空間。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評(píng)論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評(píng)論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評(píng)論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,779評(píng)論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,109評(píng)論 1 330
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,287評(píng)論 0 291
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評(píng)論 1 338
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,515評(píng)論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,750評(píng)論 1 375
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,933評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評(píng)論 1 296
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,492評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,703評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容

  • 工作以來,因?yàn)樘厥獾脑颍佑|了不少所謂的“學(xué)困生”,他們的問題五花八門,令人眼花繚亂。總而言之,言而總之,最終造...
    小史努比閱讀 341評(píng)論 0 1
  • 有人喜歡富貴的牡丹,有人喜歡嬌艷欲滴的玫瑰,有人喜歡出淤泥而不染的蓮花,有人喜歡萬古長(zhǎng)青的松柏,而我卻喜歡那默...
    血染玫瑰閱讀 373評(píng)論 0 3
  • 謝了此時(shí)雨,空了月下樽。 白了少年頭,過了昨日人。
    李唐的小詩閱讀 144評(píng)論 0 0