JVM 內(nèi)存區(qū)域與GC

本文思路
1.JVM運(yùn)行時(shí)內(nèi)存劃分
2.對(duì)象創(chuàng)建內(nèi)存動(dòng)作
3.Java線程棧
4.JVM垃圾收集器

1.JVM運(yùn)行時(shí)內(nèi)存劃分

1.1 程序計(jì)數(shù)器

當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。字節(jié)碼解釋器工作的時(shí)候就是通過改變這個(gè)計(jì)數(shù)值來選取下一條要執(zhí)行的字節(jié)碼指令。

1.2 虛擬機(jī)棧(java方法棧)

線程私有,java方法執(zhí)行時(shí)的內(nèi)存模型,每個(gè)方法執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量表、操作數(shù)棧、方法出口等信息

局部變量表其實(shí)就是一個(gè)方法內(nèi)的局部變量對(duì)應(yīng)的指針。

1.3 本地方法棧

Native 方法執(zhí)行的線程棧

1.4 方法區(qū)

線程共享區(qū)域,存儲(chǔ)已經(jīng)被虛擬機(jī)加載的類信息,常量,靜態(tài)變量,即時(shí)編譯后的代碼等數(shù)據(jù)。

運(yùn)行時(shí)常量池
方法區(qū)的一塊,保存Class文件中描述的符號(hào)引用,也會(huì)把翻譯出來的直接引用也存儲(chǔ)在這里。它的主要特征是具備動(dòng)態(tài)性,編譯器和運(yùn)行時(shí)的常量都可以進(jìn)入,比如的string 字符串常量池 intern()方法,就是利用的該特性。

1.5 堆

JVM里所管理的內(nèi)存中最大的一塊,線程共享,存放所有對(duì)象實(shí)例。GC也是針對(duì)該區(qū)進(jìn)行回收。

2.對(duì)象創(chuàng)建內(nèi)存動(dòng)作

我們new一個(gè)對(duì)象的時(shí)候,都發(fā)生了什么?

2.1 jvm看到new指令,判斷指令參數(shù)是否能在常量池中定位到這個(gè)類的符號(hào)引用(可以認(rèn)為我們的類名),檢查這個(gè)符號(hào)引用代表的類是否已被加載、解析和初始化過,沒有的話需要進(jìn)行class加載。

2.2 jvm從堆里分配內(nèi)存

2.3 分配的內(nèi)存空間都初始化為零,保證對(duì)象的實(shí)例字段再Java代碼中不賦初始值就能直接使用。

2.4 jvm對(duì)對(duì)象做一些設(shè)置。設(shè)置存放在對(duì)象頭,對(duì)象頭里主要包含一些hashcode、GC、元數(shù)據(jù)、鎖等。

2.5 執(zhí)行init方法。此刻對(duì)于JVM一個(gè)對(duì)象已經(jīng)創(chuàng)建完畢,接下來會(huì)按照程序員的一樣做一些初始化動(dòng)作。

new 對(duì)象流程示意圖

3.Java線程棧

每執(zhí)行一個(gè)方法,jvm都會(huì)為此創(chuàng)建一個(gè)棧幀,我們的一條調(diào)用鏈路,有一個(gè)一個(gè)的棧幀組成,最后形成一個(gè)線程棧,當(dāng)棧的長度過長或者超過內(nèi)存大小就好報(bào)棧溢出的異常。

基本數(shù)據(jù)類型,string這些的指針指向的是常量池。而實(shí)例對(duì)象也就統(tǒng)稱為reference類型,指向的是一個(gè)對(duì)象的引用,訪問堆中的對(duì)象具體位置有兩種方式,通過句柄池進(jìn)行句柄訪問,另一種直接通過指針。

3.1 句柄方式

java堆中會(huì)劃分一塊內(nèi)存用來作為句柄池,reference存儲(chǔ)的就是句柄地址,而句柄包含了實(shí)例數(shù)據(jù)和類型數(shù)據(jù)各自的具體地址。具有穩(wěn)定的句柄地址的優(yōu)勢(shì),當(dāng)對(duì)象移動(dòng),只會(huì)更改句柄中的實(shí)例數(shù)據(jù)指針。

3.2 直接指針訪問

reference存儲(chǔ)的直接就是對(duì)象地址,速度快,節(jié)省了一次指針定位的開銷。java中對(duì)象的訪問特別多,所以在java中該方式使用的最多。

4.JVM垃圾收集器

棧,程序計(jì)數(shù)器這些隨線程而生,隨線程而忘。而Java堆和方法區(qū)則是在運(yùn)行期間才知道會(huì)創(chuàng)建哪些對(duì)象,需要多少內(nèi)存。垃圾回收器所關(guān)注的則是這部分內(nèi)存。

如何判斷對(duì)象對(duì)象已死?
目前主流實(shí)現(xiàn)主要基于可達(dá)性分析來判定對(duì)象存活。大概思路就是通過GC ROOT 對(duì)象向下分析,不在鏈路上的對(duì)象則認(rèn)為是已死對(duì)象。

灰色表示可回收對(duì)象

哪些對(duì)象可以作為GC ROOT對(duì)象?

  • 虛擬機(jī)棧中引用的對(duì)象
  • 方法區(qū)中類靜態(tài)屬性引用的對(duì)象
  • 方法區(qū)常量引用的對(duì)象
  • 本地方法棧中引用的對(duì)象

安全區(qū)域
JVM可以通過GC ROOT快速定位到要回收的對(duì)象,但是程序是在運(yùn)行中的,對(duì)象的關(guān)系也會(huì)發(fā)生變化。JVM只有當(dāng)程序進(jìn)入安全點(diǎn)時(shí),然后暫停程序進(jìn)行回收。

安全點(diǎn)的選定是以程序是否具有長時(shí)間執(zhí)行的特征為標(biāo)準(zhǔn)進(jìn)行選定。“長時(shí)間執(zhí)行”最明顯的特征就是指令序列復(fù)用(一組),如方法跳轉(zhuǎn)、循環(huán)跳轉(zhuǎn)、異常跳轉(zhuǎn)等,具備這些功能的指令才會(huì)產(chǎn)生safePoint。

在GC發(fā)生時(shí),一般會(huì)采用主動(dòng)式中斷,jvm在要發(fā)生GC時(shí)不會(huì)干預(yù)線程,會(huì)設(shè)置一個(gè)標(biāo)志,所有的線程主動(dòng)去輪詢,當(dāng)執(zhí)行線程發(fā)現(xiàn)了這個(gè)標(biāo)識(shí)就自己中斷掛起。

回收算法
標(biāo)記清除
步驟:將內(nèi)存中需要回收的對(duì)象打上標(biāo)記,然后回收打上標(biāo)記的對(duì)象。
缺點(diǎn):會(huì)產(chǎn)生內(nèi)存碎片

復(fù)制
步驟:將內(nèi)存一分為2,需要回收的時(shí)候,將存活對(duì)象copy到另一個(gè)內(nèi)存區(qū)域,然后原來的內(nèi)存區(qū)域就只剩下待回收的對(duì)象,直接全部清除
缺點(diǎn):可使用內(nèi)存只有一半,代價(jià)太大

標(biāo)記整理
標(biāo)記清除類似,多了一個(gè)整理的過程,在清除后,會(huì)讓存活的對(duì)象向一個(gè)方向移動(dòng),最后達(dá)到內(nèi)存連續(xù),解決碎片問題。

收集器

JVM采用分代垃圾回收。在JVM的內(nèi)存空間中把堆空間分為年老代和年輕代。將大量(據(jù)說是90%以上)創(chuàng)建了沒多久就會(huì)消亡的對(duì)象存儲(chǔ)在年輕代,而年老代中存放生命周期長久的實(shí)例對(duì)象。年輕代中又被分為Eden區(qū)(圣經(jīng)中的伊甸園)、和兩個(gè)Survivor區(qū)。新的對(duì)象分配是首先放在Eden區(qū),Survivor區(qū)作為Eden區(qū)和Old區(qū)的緩沖,在Survivor區(qū)的對(duì)象經(jīng)歷若干次收集仍然存活的,就會(huì)被轉(zhuǎn)移到年老區(qū)。

按代回收

JVM提供了很多個(gè)收集器,目前主要有Serial,Serial Old,ParNew, Paralle Scavenge,Paralle Scavenge Old,CMS,G1。當(dāng)前用的最多的是 CMS和G1。
SerialSerial Old
單線程,簡單高效。進(jìn)行GC的時(shí)候會(huì)把所有工作線程停掉,是早期的回收期。serial old是專門回收老年代的。

ParNew
Serial的并發(fā)版本

Paralle ScavengeParalle Scavenge Old
采用復(fù)制算法,追求吞吐量,可以高效的利用CPU時(shí)間,盡快完成程序的運(yùn)算任務(wù),適合在后臺(tái)運(yùn)算而不需要太多交互的程序。

吞吐量 = \frac{運(yùn)行用戶代碼時(shí)間}{運(yùn)行用戶代碼時(shí)間 + 垃圾收集時(shí)間} \quad

CMS
是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。對(duì)于尤其重視服務(wù)的響應(yīng)速度,希望系統(tǒng)停頓時(shí)間最短,以給用戶帶來較好的體驗(yàn)的系統(tǒng)很適合。

采用標(biāo)記清除算法,所以缺點(diǎn)就是會(huì)有大量的空間碎片。

G1
暫時(shí)看的不是特別清楚,可以參考下
https://www.cnblogs.com/ASPNET2008/p/6496481.html
https://mp.weixin.qq.com/s/ZwlT89vsvD2e0qEuxZto3Q
自行理解。

--
參考文章
http://www.idouba.net/a-simple-example-demo-jvm-allocation-and-gc/
http://gityuan.com/2016/01/09/java-memory/
https://blog.csdn.net/top_code/article/details/51288529

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

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

  • JVM內(nèi)存區(qū)域 了解Java GC之前,必須先搞清楚JVM中內(nèi)存區(qū)域的劃分。 JVM中內(nèi)存區(qū)域大致可分為如上圖所示...
    特立獨(dú)行的豬手閱讀 2,713評(píng)論 0 13
  • 這篇文章是我之前翻閱了不少的書籍以及從網(wǎng)絡(luò)上收集的一些資料的整理,因此不免有一些不準(zhǔn)確的地方,同時(shí)不同JDK版本的...
    高廣超閱讀 16,049評(píng)論 3 83
  • 1 CPU和內(nèi)存的交互 了解jvm內(nèi)存模型前,了解下cpu和計(jì)算機(jī)內(nèi)存的交互情況?!疽?yàn)镴ava虛擬機(jī)內(nèi)存模型定義...
    Garwer閱讀 374,335評(píng)論 54 551
  • jvm原理 Java虛擬機(jī)是整個(gè)java平臺(tái)的基石,是java技術(shù)實(shí)現(xiàn)硬件無關(guān)和操作系統(tǒng)無關(guān)的關(guān)鍵環(huán)節(jié),是java...
    AI喬治閱讀 17,548評(píng)論 21 486
  • 從事理論研究、藝術(shù)創(chuàng)作的思維層次較高的人大多不喜歡忙碌,比如哲學(xué)家、藝術(shù)家等等,他們寧愿“終日默然、面壁而坐”地...
    精進(jìn)的醫(yī)生閱讀 357評(píng)論 2 1

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