jvm內(nèi)存結(jié)構(gòu),java內(nèi)存模型,java對象布局還分不清?快來圍觀!

hello 我是寶哥 , 接上一篇文章,我們聊到了

JVM加載類的流程

有面試官會(huì)讓你解釋一下Java的內(nèi)存模型,有些人解釋對了,結(jié)果面試官說不對,應(yīng)該是堆啊、棧啊、方法區(qū)什么的(遇到這種面試官,就是你裝逼的時(shí)刻了..)

看完本篇文章你將了解:

  • 1.JVM內(nèi)存結(jié)構(gòu)
  • 2.JVM棧幀剖析
  • 3.方法區(qū)在JDK1.7和1.8中的區(qū)別
  • 4.堆分代結(jié)構(gòu)

建議收藏!

JVM內(nèi)存結(jié)構(gòu)

首先JVM內(nèi)存結(jié)構(gòu)JAVA內(nèi)存模型是兩個(gè)概念.

JVM內(nèi)存結(jié)構(gòu):

Class文件通過類加載機(jī)制 加載到內(nèi)存空間,JVM內(nèi)存結(jié)構(gòu)就是上圖中內(nèi)存空間,Java內(nèi)存模型,則是另外的一個(gè)概念.

根據(jù)《Java 虛擬機(jī)規(guī)范(Java SE 7 版)》規(guī)定,Java 虛擬機(jī)所管理的內(nèi)存如下圖所示。


從上圖可看出,運(yùn)行時(shí)數(shù)據(jù)區(qū)分為五大區(qū)域。這些區(qū)域各有各的用途,其中方法區(qū)和堆是所有線程共享的,棧,本地方法棧和程序計(jì)數(shù)器則為線程私有的。

1.程序計(jì)數(shù)器

程序計(jì)數(shù)器是一塊很小的內(nèi)存空間,它是線程私有的,可以認(rèn)作為當(dāng)前線程的行號(hào)指示器。

為什么要程序計(jì)數(shù)器呢

因?yàn)镃PU會(huì)在多個(gè)線程中切換上下文,需要使用程序計(jì)數(shù)器紀(jì)錄當(dāng)前線程運(yùn)行到哪一行了,等待線程重新獲取到運(yùn)行時(shí)間時(shí),繼續(xù)從計(jì)數(shù)的位置往下執(zhí)行.至于它是線程私有的,是因?yàn)槊總€(gè)線程都需要獨(dú)立計(jì)數(shù),各個(gè)線程之間不會(huì)產(chǎn)生影響.

面試題:程序計(jì)數(shù)器會(huì)發(fā)生OutOfMemoryError嗎?

答:這塊內(nèi)存區(qū)域是虛擬機(jī)規(guī)范中唯一沒有OutOfMemoryError的區(qū)域。如果線程執(zhí)行的是個(gè)java方法,那么計(jì)數(shù)器記錄虛擬機(jī)字節(jié)碼指令的地址。如果為native方法,那么計(jì)數(shù)器為空。

2.Java棧(JVM線程棧)

首先我們要記住,棧描述的是方法執(zhí)行的內(nèi)存模型,它是線程私有的.

每個(gè)方法被執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量表,操作棧,動(dòng)態(tài)鏈接,方法出口等信息。每一個(gè)方法被調(diào)用的過程就對應(yīng)一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過程,如下圖:

面試題:Java虛擬機(jī)棧可能出現(xiàn)哪兩種類型的異常?

  • 1 虛擬機(jī)棧是一個(gè)棧,當(dāng)我們的棧幀超過最大深度時(shí),會(huì)拋出StackOverflowError
  • 2 棧無法申請到足夠的空間時(shí),拋出OutOfMemoryError異常

棧幀(Stack Frame)

每一個(gè)方法從調(diào)用至執(zhí)行完成的過程,都對應(yīng)著一個(gè)棧幀在虛擬機(jī)棧里從入棧到出棧的過程,那么一個(gè)棧幀包含什么?

  • 局部變量表(Local Variable Table)
    也叫本地變量表,它所需要的內(nèi)存空間在編譯期完成分配,當(dāng)進(jìn)入一個(gè)方法時(shí),這個(gè)方法在棧中需要分配多大的局部變量空間是完全確定的,在方法運(yùn)行期間不會(huì)改變,它的最小單位為Slot,一個(gè)Slot可以存放一個(gè)32位以內(nèi)的數(shù)據(jù)類型.虛擬機(jī)通過索引定位的方法查找相應(yīng)的局部變量,索引的范圍是從0~局部變量表最大容量。如果Slot是32位的,則遇到一個(gè)64位數(shù)據(jù)類型的變量(如long或double型),則會(huì)使用兩個(gè)連續(xù)的Slot來存儲(chǔ)

  • 操作數(shù)棧(Operand Stack)
    也常稱為操作棧,它是一個(gè)后入先出棧(LIFO)。當(dāng)一個(gè)方法剛剛開始執(zhí)行時(shí),其操作數(shù)棧是空的,隨著方法執(zhí)行和字節(jié)碼指令的執(zhí)行,會(huì)從局部變量表或?qū)ο髮?shí)例的字段中復(fù)制常量或變量寫入到操作數(shù)棧,再隨著計(jì)算的進(jìn)行將棧中元素出棧到局部變量表或者返回給方法調(diào)用者,也就是出棧/入棧操作。一個(gè)完整的方法執(zhí)行期間往往包含多個(gè)這樣出棧/入棧的過程。

  • 動(dòng)態(tài)連接(Dynamic Linking)
    在一個(gè)class文件中,一個(gè)方法要調(diào)用其他方法,需要將這些方法的符號(hào)引用轉(zhuǎn)化為其在內(nèi)存地址中的直接引用,而符號(hào)引用存在于方法區(qū)中的運(yùn)行時(shí)常量池。
    Java虛擬機(jī)棧中,每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧所屬方法的符號(hào)引用,持有這個(gè)引用的目的是為了支持方法調(diào)用過程中的動(dòng)態(tài)連接(Dynamic Linking)。
    這些符號(hào)引用一部分會(huì)在類加載階段或者第一次使用時(shí)就直接轉(zhuǎn)化為直接引用,這類轉(zhuǎn)化稱為靜態(tài)解析。另一部分將在每次運(yùn)行期間轉(zhuǎn)化為直接引用,這類轉(zhuǎn)化稱為動(dòng)態(tài)連接

  • 方法返回地址(Return address)
    一般方法執(zhí)行時(shí),有2種方式會(huì)退出該方法

    • 1.正常退出
      正常退出指方法正常完成并退出,沒有拋出任何異常,當(dāng)前方法正常完成,則根據(jù)當(dāng)前方法返回的字節(jié)碼指令,這時(shí)有可能會(huì)有返回值傳遞給方法調(diào)用者(調(diào)用它的方法),或者無返回值(void)。具體是否有返回值以及返回值的數(shù)據(jù)類型將根據(jù)該方法返回的字節(jié)碼指令確定。
    • 2.異常退出
      方法執(zhí)行過程中遇到異常,并且這個(gè)異常在方法體內(nèi)部沒有得到處理,導(dǎo)致方法退出,只要在本方法的異常表中沒有搜索到相應(yīng)的異常處理器,就會(huì)導(dǎo)致方法退出。

    方法退出過程實(shí)際上就等同于把當(dāng)前棧幀出棧,因此退出可以執(zhí)行的操作有:恢復(fù)上層方法的局部變量表和操作數(shù)棧,把返回值(如果有的話)壓如調(diào)用者的操作數(shù)棧中,調(diào)整PC計(jì)數(shù)器的值以指向方法調(diào)用指令后的下一條指令。
    一般來說,方法正常退出時(shí),調(diào)用者的PC計(jì)數(shù)值可以作為返回地址,棧幀中可能保存此計(jì)數(shù)值。而方法異常退出時(shí),返回地址是通過異常處理器表確定的,棧幀中一般不會(huì)保存此部分信息

  • 附加信息
    指的是在虛擬機(jī)實(shí)現(xiàn)中加入了一些規(guī)范里沒有描述的信息到棧幀之中,例如與調(diào)試相關(guān)的信息。

本地方法棧

本地方法棧是與虛擬機(jī)棧發(fā)揮的作用十分相似,區(qū)別是虛擬機(jī)棧執(zhí)行的是Java方法(也就是字節(jié)碼)服務(wù),而本地方法棧則為虛擬機(jī)使用到的native方法服務(wù),可能底層調(diào)用的c或者c++,我們打開jdk安裝目錄可以看到也有很多用c編寫的文件,可能就是native方法所調(diào)用的c代碼。

java是跨平臺(tái)的語言,既然是跨了平臺(tái),所付出的代價(jià)就是犧牲一些對底層的控制,而java要實(shí)現(xiàn)對底層的控制,就需要一些其他語言的幫助,這個(gè)就是native的作用了.

它是所有線程共享的,它的目的是存放對象實(shí)例。同時(shí)它也是GC所管理的主要區(qū)域,因此常被稱為GC堆,現(xiàn)在收集器常使用分代算法進(jìn)行垃圾回收,這里是垃圾回收重災(zāi)區(qū).

堆分代結(jié)構(gòu)

堆被劃分成兩個(gè)不同的區(qū)域:新生代 ( Young )、老年代 ( Old )。

新生代 ( Young ) 又被劃分為三個(gè)區(qū)域:Eden、From Survivor、To Survivor。它們的默認(rèn)比例關(guān)系如下圖:


  • 新生代 (Young Generation)
    新生成的對象優(yōu)先存放在新生代中,新生代對象朝生夕死,存活率很低,在新生代中常規(guī)應(yīng)用進(jìn)行一次垃圾收集一般可以回收70%~95%的空間,回收效率很高

  • 幸存者區(qū) (Survivor)
    幸存者區(qū)有2塊, From Survivor區(qū), To Survivor區(qū),也稱S0,S1區(qū).被面試官問了不要懵,GC進(jìn)行時(shí),Eden區(qū)中所有存活的對象都會(huì)被復(fù)制到 To Survivor區(qū),而在FromSurvivor區(qū)中,仍存活的對象會(huì)根據(jù)它們的年齡值決定去向,年齡值達(dá)到年齡閥值(默認(rèn)為15,新生代中的對象毎熬過一輪垃圾回收,年齡值就加1,GC分代年齡存儲(chǔ)在對象的 header中)的對象會(huì)被移到老年代中,沒有達(dá)到閥值的對象會(huì)被復(fù)制到 To Survivor區(qū)。接著清空Eden區(qū)和 From Survivor區(qū),新生代中存活的對象都在 To Survivor區(qū)。接著,F(xiàn)rom Survivor區(qū)和 To Survivor區(qū)會(huì)交換它們的角色,GC時(shí)當(dāng) To Survivor區(qū)沒有足夠的空間存放上一次新生代收集下來的存活對象時(shí),需要依賴?yán)夏甏M(jìn)行分配擔(dān)保,將這些對象存放在老年代中.

  • 老年代(Old)
    在新生代中經(jīng)歷了多次(具體看虛擬機(jī)配置的值)GC后仍然存活下來的對象會(huì)進(jìn)入老年代中。老年代中的對象生命周期較長,存活率比較高,在老年代中進(jìn)行GC的頻率相對而言較低,而且回收的速度也比較慢


常見面試題:

  • 何時(shí)發(fā)生Minor GC/Young GC ?
    新生成的對象在Eden區(qū)分配(大對象除外,大對象直接進(jìn)入老年代),當(dāng)Eden區(qū)沒有足夠的空間進(jìn)行分配時(shí),虛擬機(jī)將發(fā)起一次 Minor GC也叫Young GC
  • 為什么分代?
    將對象根據(jù)存活概率進(jìn)行分類,對存活時(shí)間長的對象,放到固定區(qū),從而減少掃描垃圾時(shí)間及GC頻率。針對分類進(jìn)行不同的垃圾回收算法,對算法揚(yáng)長避短

方法區(qū)


從這張圖可以看到JDK1.8和JDK1.7相比最大的區(qū)別是:元空間區(qū)取代了永久代,永久代原本主要存放ClassMeta的信息。而元空間的本質(zhì)和永久代類似,都是對JVM規(guī)范中方法區(qū)的實(shí)現(xiàn)。不過元空間與永久代之間最大的區(qū)別在于:元空間并不在虛擬機(jī)中,而是使用本地內(nèi)存。因此,默認(rèn)情況下,元空間的大小僅受本地內(nèi)存限制。

上圖中我們可以看到,JDK1.7和1.8對于運(yùn)行時(shí)數(shù)據(jù)區(qū)和堆中的方法區(qū)都做了調(diào)整,jdk8中引入了一個(gè)新的內(nèi)存區(qū)域叫metaspace。并不是所有的jvm中都有永久代,IBM的J9,oracle的JRocket都沒有永久代,永久代是實(shí)現(xiàn)層面的東西,永久代里面存的東西基本上就是方法區(qū)規(guī)定的那些東西。


面試題: 方法區(qū)、永久代、元空間的區(qū)別?

  • 方法區(qū)
    是JVM的規(guī)范,所有虛擬機(jī)必須遵守的。是JVM 所有線程共享的、用于存儲(chǔ)類的信息、常量池、方法數(shù)據(jù)、方法代碼等。
  • 永久代:
    全稱Permanent Generation space ,是指內(nèi)存的永久保存區(qū)域。是 HotSpot 虛擬機(jī)基于JVM規(guī)范對方法區(qū)的一個(gè)落地實(shí)現(xiàn),并且只有 HotSpot 才有 PermGen space,在JDK8被移除了.
  • 元空間:
    元空間與永久代之間最大的區(qū)別在于:元空間并不在虛擬機(jī)中,而是使用本地內(nèi)存。它也是基于JVM規(guī)范對方法區(qū)的一個(gè)落地實(shí)現(xiàn)

JDK6、JDK7 時(shí),方法區(qū) 就是 PermGen(永久代)。
JDK8 時(shí),方法區(qū)就是 Metaspace(元空間)

不同版本的JDK中 堆溢出的實(shí)例:


面試題:為什么去除了永久代:

1)字符串存在永久代中,容易出現(xiàn)性能問題和內(nèi)存溢出。

2)類及方法的信息等比較難確定其大小,因此對于永久代的大小指定比較困難,太小容易出現(xiàn)永久代溢出,太大則容易導(dǎo)致老年代溢出。

3)永久代會(huì)為 GC 帶來不必要的復(fù)雜度,并且回收效率偏低。

結(jié)語

經(jīng)過本篇學(xué)習(xí),我們已經(jīng)知道JVM對于內(nèi)存的管理,以及它的結(jié)構(gòu),

千萬不要搞混 JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)和JMM(Java memory modle)的關(guān)系

而JAVA對象布局在我的一篇文章中有講過他們是不同的概念
JAVA對象布局

下一篇我們來研究JMM(java的內(nèi)存模型)和Java中的逃逸分析,以及多線程編程延伸.
請持續(xù)關(guān)注公眾號(hào):JAVA寶典

關(guān)注公眾號(hào):java寶典

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

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