不止面試02-JVM內(nèi)存模型面試題詳解

第一部分:面試題

本篇文章我們將嘗試回答以下問題:

  1. 描述一下jvm的內(nèi)存結(jié)構(gòu)
  2. 描述一下jvm的內(nèi)存模型
  3. 談一下你對常量池的理解
  4. 什么情況下會發(fā)生棧內(nèi)存溢出滓窍?和內(nèi)存溢出有什么不同邦蜜?
  5. String str = new String(“abc”)創(chuàng)建了多少個實例?

第二部分:深入原理

ok,開始。怎們還是先講原理卤档,再說答案劝枣。如果時間不足舔腾,也可以直接跳到最后看答案。

本次分享我們主要圍繞jvm內(nèi)存結(jié)構(gòu)展開采桃,這也是java面試必考知識點之一普办。所以我們先來看看jvm內(nèi)存結(jié)構(gòu)到底是啥樣子。

1. jvm內(nèi)存模型

我們首先看下面這張圖:


file

這張圖是虛擬機的結(jié)構(gòu)圖舆驶,當(dāng)我們在討論jvm內(nèi)存模型時拘荡,指的就是中間五彩的那條區(qū)域:運行時數(shù)據(jù)區(qū)(runtime data area)珊皿。

我們把這個區(qū)域單獨畫出來,如下圖所示:

file

現(xiàn)在我們來看看每個區(qū)域的用途

1.1 棧(Stack)

每當(dāng)一個線程去執(zhí)行方法時,就會同時在棧里面創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表抄淑、操作數(shù)棧、動態(tài)鏈接、方法出口等。

每一個方法從被調(diào)用到執(zhí)行完成的過程钠乏,都對應(yīng)著一個棧幀從入棧到出棧的過程。

棧是線程私有的俏拱,每個線程在棧中保有自己的數(shù)據(jù)锅必,別的線程無法訪問驹愚。

在棧中我們可能遇到兩種異常:StackOverflowError和OutOfMemoryError

StackOverflowError是指線程請求的棧深度大于虛擬機所允許的深度

OutOfMemoryError是指棧擴展時無法申請到足夠的內(nèi)存

這兩種異常我們會在后面的文章中詳細講到。

1.2 本地方法棧(Native Method Stack)

本地方法棧和棧差不多蒸甜,區(qū)別只在于本地方法棧為Native方法服務(wù)。

Native 方法就是一個Java調(diào)用非Java代碼的接口恨憎,比如JNI。

本地方法棧也是線程私有的钥组。

1.3 程序計數(shù)器(PC Register)

要理解程序計數(shù)器,我們需要知道java代碼最終都要編譯成一條條的字節(jié)碼屿附,然后由字節(jié)碼解釋器一條條的執(zhí)行的。而程序計數(shù)器可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號計數(shù)器

如果正在執(zhí)行的是一個java方法各聘,那么這個計數(shù)器記錄的是正在執(zhí)行的字節(jié)碼指令地址。如果正在執(zhí)行的是Native方法搁嗓,那么計數(shù)器的值為Undefined。

程序計數(shù)器也是線程私有的棍矛,每條線程都有一個獨立的程序計數(shù)器,各線程的程序計數(shù)器互不影響。

程序計數(shù)器是唯一一個不會OOM的內(nèi)存區(qū)域潘拨。

1.4堆(Heap)

堆應(yīng)該是java內(nèi)存中占用空間最大的一個區(qū)域茫船,大家喜聞樂見的垃圾回收就主要發(fā)生在這個區(qū)域狰闪。

堆的唯一作用就是存放對象幔欧,不過并非所有對象都在堆中觉义。這個我們以后會講到。

堆如果空間不足徒坡,就會拋出OOM異常。

堆是可以讓多個線程共享的

1.5 方法區(qū)(Method Area)

方法區(qū)也是可以讓多個線程共享的

方法區(qū)主要用來存放類的版本,字段,方法浮入,接口野舶、常量池和運行時常量池。

常量池里存儲著字面量和符號引用

還記得我們在jvm類加載面試題詳解里說到的“加載”過程嗎?其中說到“類加載器把類讀入內(nèi)存”弟翘。這里的所說的“內(nèi)存”滚躯,指的就是方法區(qū)宙帝。

和方法區(qū)相關(guān)的知識點有永久代靴患、元空間仍侥、常量池等。這幾個概念非常容易把人繞暈鸳君,所以接下來我會盡量畫圖說明农渊,給大家講清楚。

1.5.1 永久代與元空間

作為和堆一樣可以讓線程共享的區(qū)域或颊,堆之外的的空間被叫做非堆(Non-Heap)砸紊。可以粗略地理解為非堆里包含了永久代囱挑,而永久代里又包括了方法區(qū)醉顽。

我們常常把永久代和方法區(qū)等同起來,然而永久代其實是Hotspot虛擬機把分代GC的范圍擴展到方法區(qū)的產(chǎn)物平挑。(分代GC我們會在后面講到)游添。如下圖:

file

所以,永久代和方法區(qū)雖然基本上指的是同一片內(nèi)存區(qū)域通熄,但是實質(zhì)上是有差別的否淤。

而到了jdk1.8中,永久代被取消棠隐,取而代之的便是元空間

元空間跟永久代最大的區(qū)別就在于檐嚣,元空間直接使用機器內(nèi)存助泽,不在jvm虛擬機內(nèi)啰扛。所以理論上你的機器內(nèi)存有多大,元空間就能有多大嗡贺。

此外隐解,之前永久代的符號引用(Symbols)轉(zhuǎn)移到了堆外內(nèi)存(native heap);字面量(interned strings)和類的靜態(tài)變量(class statics)轉(zhuǎn)移到了堆內(nèi)(heap)诫睬。

這樣做的好處在于可以減少OOM煞茫,同時方便HotSpot和JRockit合并。

1.5.2 虛擬機的多實現(xiàn)

上一個小節(jié)我們提到了HotSpot和JRockit摄凡,這是個啥呢续徽?

要知道,jdk的全稱是Java Development Kit 亲澡,Java開發(fā)工具包。可以簡單認(rèn)為胳挎,jdk就是各種java開發(fā)工具+java虛擬機帜讲。而HotSpot就是目前Oracle jdk默認(rèn)使用的虛擬機。

我們可以用java -version查看目前使用的虛擬機:

~ java -version
java version "1.8.0_151"
Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)

看見最后一行了嗎癞己?“Java HotSpot(TM) 64-Bit”說明我目前正在用的就是HotSpot膀斋。

除了HotSpot之外,還有很多其他的虛擬機痹雅,比如JRockit就是由BEA公司開發(fā)的一款java虛擬機仰担,號稱世界最快。

后來 JRockit被Oracle收購练慕,于是Oracle就同時擁有了HotSpot和JRockit兩款虛擬機惰匙,這也是為什么要把它們合并的原因之一。

1.5.3 各種常量池

上面我們提到了常量池铃将、運行時常量池和字符串常量池项鬼,現(xiàn)在我們來看看這幾個池子到底是個什么關(guān)系。

以下為jdk1.7的情況劲阎。

首先還是來張圖:

file

數(shù)據(jù)是怎么在這幾個池子里流轉(zhuǎn)的呢绘盟?

首先,當(dāng)類被加載的時候悯仙,class文件就會被加載到方法區(qū)龄毡,里面有塊區(qū)域就被稱為常量池。常量池主要存儲兩個東西:字面量(Literal)和符號引用(Symbolic References)锡垄。

下面這個圖清楚的展示了常量池的內(nèi)容:

file

當(dāng)程序運行到該類的時候沦零,常量池的大部分內(nèi)容都會進入運行時常量池。但是String不同货岭,String對象會存在堆里路操,然后在字符串常量池保存一個引用疾渴。

當(dāng)主線程開始實例化字符串的時候,先到字符串常量池找有沒有相等的字符串屯仗,有的話就直接把引用賦值給變量搞坝。
不過,需要注意的是魁袜,如果以創(chuàng)建對象的方式創(chuàng)建字符串桩撮,比如new String("abc"),那么峰弹,會在內(nèi)存中開辟一塊全新的內(nèi)存地址店量,創(chuàng)建一個新對象,然后把引用賦值給變量垮卓,就沒有字符串常量池的事了垫桂。

1.6 直接內(nèi)存(Direct Memory)

看到這兒你可能要問了,what粟按?直接內(nèi)存是什么鬼诬滩?

直接內(nèi)存并不是虛擬機運行時數(shù)據(jù)區(qū)的一部分,但是這部分區(qū)域卻可以被虛擬機使用灭将,且使用不當(dāng)也會OOM疼鸟,所以干脆在這講一下。

在jdk1.4中加入的NIO類引入了一種基于通道和緩沖區(qū)的IO方式庙曙,可以直接分配堆外內(nèi)存空镜,并操作。理論上來說捌朴,直接內(nèi)存由于不在虛擬機內(nèi)吴攒,所以不受虛擬機內(nèi)存大小控制(是不是有點像元空間?)砂蔽。但是如果這塊空間太大洼怔,可能會擠占虛擬機的內(nèi)存,畢竟物理內(nèi)存有限左驾,從而使虛擬機在動態(tài)擴展的時候由于空間不足而拋出OOM镣隶。

jvm內(nèi)存結(jié)構(gòu)到此結(jié)束,下面我們來說說java內(nèi)存模型诡右。

1.7 java7和java8的jvm內(nèi)存模型區(qū)別

看下面這張圖:

file

java8中安岂,元空間(METASPACE)取代了永久代(PREM GEN),并且移到了堆外內(nèi)存(Native Memory)中帆吻。

2. Java內(nèi)存模型

看到這你是不是在想:什么鬼域那?難道剛剛我們說的不是java內(nèi)存模型嗎?

實際上猜煮,jvm內(nèi)存模型和java內(nèi)存模型的確是兩個比較容易混淆的概念琉雳。

java內(nèi)存模型是指Java Memory Model样眠,簡稱JMM。用于屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問差異翠肘,以實現(xiàn)讓Java程序在各種平臺下都能達到一致的并發(fā)效果,JMM規(guī)范了Java虛擬機與計算機內(nèi)存是如何協(xié)同工作的:規(guī)定了一個線程如何和何時可以看到由其他線程修改過后的共享變量的值辫秧,以及在必須時如何同步的訪問共享變量束倍。

好吧,上面這段話著實不大好理解盟戏。我們化繁為簡绪妹,主要記住JMM規(guī)范了程序中變量的訪問規(guī)則,保證了操作的原子性柿究、可見性邮旷、有序性。

file

第三部分:面試題答案

1.描述一下jvm內(nèi)存模型

jvm內(nèi)存模型分為5個區(qū)域蝇摸,其中線程獨占的區(qū)域是棧婶肩、本地方法棧、程序計數(shù)器貌夕,線程共享的區(qū)域是堆律歼、方法區(qū)。

2.描述一下java內(nèi)存模型

回答這個問題一定要問清楚是不是要問java內(nèi)存模型啡专,確認(rèn)是的話险毁,可以回答:java內(nèi)存模型規(guī)定了變量的訪問規(guī)則,保證操作的原子性们童、可見行畔况、有序性。

3.談一下你對常量池的理解

常量池是類在編譯后儲存常量的一塊區(qū)域慧库,當(dāng)程序運行到該類跷跪,常量池的大部分?jǐn)?shù)據(jù)會進入運行時常量池,字符串會進入字符串常量池完沪。

4.什么情況下會發(fā)生棧內(nèi)存溢出域庇?和內(nèi)存溢出有什么不同?

棧內(nèi)存溢出指程序調(diào)用的棧深度多大或椄不空間不足時拋出的StackOverflowError听皿。

一般所謂內(nèi)存溢出指的是堆內(nèi)存溢出,拋出的是OutOfMemoryError:java heap space宽档。

在jdk1.7中尉姨,還可能出現(xiàn)永久代內(nèi)存溢出,拋出的是OutOfMemoryError: PermGen space

在jdk1.8中吗冤,則會出現(xiàn)元空間溢出又厉,拋出的是OutOfMemoryError:MetaSpace

5.String str = new String(“abc”)創(chuàng)建了多少個實例九府?

雖然很多博客都告訴我們創(chuàng)建了兩個對象:一個字符串a(chǎn)bc對象和一個字符串常量池里指向abc的引用對象。

但實際情況要更復(fù)雜一點覆致。

實際上在執(zhí)行完String str = new String(“abc”)之后侄旬,其實只創(chuàng)建了一個對象:堆里的字符串對象。而str直接指向該對象煌妈。在執(zhí)行intern()方法后儡羔,才會到字符串常量池創(chuàng)建引用對象。當(dāng)然有時候這個過程會自動完成璧诵,但情況比較復(fù)雜汰蜘,難以確定。

有很多面試官其實自己也搞不清之宿,所以不妨先告訴他創(chuàng)建了兩個對象族操,然后再分析一番,效果更好比被。

引用文獻

JVM虛擬機種類

什么是HotSpot VM

永久代(PermGen)和元空間的區(qū)別(Metaspace)

JVM的Heap Memory和Native Memory

JAVA8里的native heap究竟是個什么概念色难?

方法區(qū)和常量池

java用這樣的方式生成字符串:String str = "Hello",到底有沒有在堆中創(chuàng)建對象姐赡?

Java內(nèi)存模型(JMM)總結(jié)

JVM內(nèi)存溢出詳解
系列文章總目錄:https://mp.weixin.qq.com/s/56JgXLArTAEDj1f3y4arLA

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末莱预,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子项滑,更是在濱河造成了極大的恐慌依沮,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件枪狂,死亡現(xiàn)場離奇詭異危喉,居然都是意外死亡,警方通過查閱死者的電腦和手機州疾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門辜限,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人严蓖,你說我怎么就攤上這事薄嫡。” “怎么了颗胡?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵毫深,是天一觀的道長。 經(jīng)常有香客問我毒姨,道長哑蔫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮闸迷,結(jié)果婚禮上嵌纲,老公的妹妹穿的比我還像新娘。我一直安慰自己腥沽,他們只是感情好逮走,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著巡球,像睡著了一般言沐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上酣栈,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機與錄音汹押,去河邊找鬼矿筝。 笑死,一個胖子當(dāng)著我的面吹牛棚贾,可吹牛的內(nèi)容都是我干的窖维。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼妙痹,長吁一口氣:“原來是場噩夢啊……” “哼铸史!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起怯伊,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤琳轿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后耿芹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體崭篡,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年吧秕,在試婚紗的時候發(fā)現(xiàn)自己被綠了琉闪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡砸彬,死狀恐怖颠毙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情砂碉,我是刑警寧澤蛀蜜,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站绽淘,受9級特大地震影響涵防,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一壮池、第九天 我趴在偏房一處隱蔽的房頂上張望偏瓤。 院中可真熱鬧,春花似錦椰憋、人聲如沸厅克。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽证舟。三九已至,卻和暖如春窗骑,著一層夾襖步出監(jiān)牢的瞬間女责,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工创译, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留抵知,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓软族,卻偏偏與公主長得像刷喜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子立砸,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

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