JVM筆記02-JVM內(nèi)存區(qū)域結(jié)構(gòu)

0. 前言

JVM筆記系列,以JDK1.7為基準(zhǔn),主要以《深入理解Java虛擬機(jī)》(第二版)和《Java虛擬機(jī)規(guī)范(Java SE 7版)》 為參考,主要包括下圖所示的五部分內(nèi)容:1.類加載,2.內(nèi)存區(qū)域,3.垃圾回收,4.JVM參數(shù),5.JVM監(jiān)控工具。

本人是Java程序員,重點關(guān)注這些有助于優(yōu)化開發(fā)、性能調(diào)優(yōu)、問題解決等這些和具體生產(chǎn)密切相關(guān)的部分;關(guān)于Class的文件結(jié)構(gòu)、編譯、指令等部分,可以閱讀上述書籍或其它材料。

jvm.png

本文主要記錄JVM內(nèi)存區(qū)域結(jié)構(gòu)的相關(guān)知識,本文的主要知識點如下:

jvm內(nèi)存區(qū)域結(jié)構(gòu).png

1. JVM內(nèi)存區(qū)域結(jié)構(gòu)

JVM定義了若干程序運(yùn)行期使用到的數(shù)據(jù)區(qū),其中一些隨著JVM進(jìn)程啟動而創(chuàng)建,隨著JVM退出而銷毀;另一些則是與線程一一對應(yīng),隨著線程的啟動和結(jié)束而建立和銷毀。JVM的運(yùn)行時數(shù)據(jù)區(qū)分為5個部分,如下圖所示,分別是程序計數(shù)器、Java棧、Native方法棧、堆、方法區(qū)。

jvm-runtime-area.png

1.1 程序計數(shù)器(Program Counter)

  • 程序計數(shù)器占用非常小的內(nèi)存,指向下一條指令的地址。
  • 每個線程擁有一個程序計數(shù)器。
  • 程序計數(shù)器在線程創(chuàng)建時創(chuàng)建。
  • 如果是Java方法,程序計數(shù)器指向字節(jié)碼指令的地址。
  • 如果是Native方法,程序計數(shù)器值則為空(Undefined)。
  • 程序計數(shù)器不會出現(xiàn)OutOfMemoryError。

1.2 Java棧

  • Java棧是線程私有的,生命周期和線程相同。
  • 棧是由一系列棧幀組成的。
  • 每個棧幀用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。
  • 每一個方法被調(diào)用到執(zhí)行完成的過程,就是一個棧幀在JVM從入棧到出棧的過程。
jvm-stack-frame.png

JVM規(guī)范中描述,Java棧可能會出現(xiàn)兩種異常。

  • StackOverflowError:線程請求的棧深大于虛擬機(jī)所允許的深度(例如無限遞歸)。
  • OutOfMemoryError:虛擬機(jī)棧可以動態(tài)擴(kuò)展,如果擴(kuò)展無法申請足夠的內(nèi)存時,就會報出。

1.3 Native方法棧

本地方法棧和Java棧是非常相似的,Java棧是為了執(zhí)行Java方法服務(wù),本地方法棧是為了執(zhí)行Native方法使用。在HotSpot虛擬機(jī)中,Java棧和本地方法棧合二為一。

1.4 堆(Heap)

  • Java堆是JVM所管理的內(nèi)存中最大的一塊,生命周期和JVM進(jìn)程相同。
  • 用于存放對象實例,幾乎所有的對象都在Heap上。
  • Java堆是所有線程共享的空間。

從垃圾回收的角度來說,Java堆分為新生代和老生代,其中新生代還分為Eden、From Survivor(S0)、To Survivor(S1)三部分,如下圖所示。


jvm-heap.png

默認(rèn)參數(shù)下,新生代:老生代 = 1:2,Eden:Survivor = 8:1。Java堆中最大可用內(nèi)存 = 老生代+ Eden + Survivor*1,即S0和S1永遠(yuǎn)有一個處于閑置的狀態(tài),GC的時JVM候會把其中一個Survivor中存活的對象復(fù)制到另一個Survivor中。

  • Eden區(qū)是Java實例對象優(yōu)先分配的區(qū)域,如果Eden沒有足夠的空間,將會執(zhí)行一次Minor GC。
  • 經(jīng)過Minor GC后,Eden+S0(或者S1)中還存活的對象將會轉(zhuǎn)移到S1中,然后S0會被清空。
  • Survivor中放不下的、存活次數(shù)超過一定數(shù)目的對象,會被轉(zhuǎn)移到老年代(Old)空間,大對象也可能會直接分配到老年代(Old)空間。
  • 當(dāng)老年代(Old)空間不夠時,將會發(fā)生Major GC。
  • 如果垃圾回收后,仍然沒有足夠的空間,那么將會拋出OutOfMemoryError。

1.5 方法區(qū)

  • 方法區(qū)是線程共享的空間,生命周期和JVM進(jìn)程相同。
  • 方法區(qū)用于存儲類的信息、常量池、字段和方法數(shù)據(jù)、字節(jié)碼內(nèi)容等。

在我們常用的HotSpot虛擬機(jī)中,JDK1.7之前,使用PermGen(永久代)來實現(xiàn)方法區(qū);在JDK1.8中完全移除了PermGen,改用Metaspace(元空間)來實現(xiàn)方法區(qū)。

其實,移除PermGen的工作從JDK1.7就開始了,符號引用(Symbols)、字面量(interned strings)、類的靜態(tài)變量(class statics)在1.7中都轉(zhuǎn)移到了Heap中,這大大減少了PermGen拋出OutOfMemoryError的機(jī)會。

Metaspace使用的是本地內(nèi)存,而非JVM內(nèi)存;因此Metaspace的大小限制,受限于物理內(nèi)存的的限制;當(dāng)然它是可以通過參數(shù)-XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 來指定的。

方法區(qū)的空間不夠用了,將會拋出OutOfMemoryError。

關(guān)于方法區(qū),運(yùn)行時常量池特別值得一提,運(yùn)行時常量池中的常量,基本來源于各個class文件中的常量池;程序運(yùn)行時,除非手動向常量池中添加常量(比如調(diào)用String.intern方法),否則jvm不會自動添加常量到常量池。

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

直接內(nèi)存并不是JVM運(yùn)行時數(shù)據(jù)區(qū)的一部分,屬于堆外(off-heap)內(nèi)存,也不是JVM規(guī)范中定義的內(nèi)存區(qū)域。JDK1.4新增了NIO包,引入了一種基于Channel和Buffer的IO方式,可以使用Native方法直接分配堆外內(nèi)存,然后通過存儲在Java堆中的DirectByteBuffer對象作為這塊內(nèi)存的引用進(jìn)行操作。

//見 java.nio.ByteBuffer
public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
}

// native方法,見sun.misc.Unsafe類
public native long allocateMemory(long var1);

使用堆外內(nèi)存,可以擴(kuò)展使用更大的內(nèi)存空間,理論上能減少GC的暫停時間,還可以在進(jìn)程間共享(MappedByteBuffer和FileChannel)。

Direct Memory默認(rèn)的大小是等同于JVM最大堆,我們可以通過-XX:MaxDirectMemorySize參數(shù)來控制其大小。

如果直接內(nèi)存空間不夠用了,將會拋出OutOfMemoryError。

2. 對象的創(chuàng)建和訪問過程

2.1 對象的創(chuàng)建過程

  1. 類加載檢測。當(dāng)new對象的時候,將會檢查能否在常量池中定位到一個類的符號引用,并檢查這個類是否被加載、解析和初始化,如果沒有,則執(zhí)行相應(yīng)的類加載過程。

  2. 類加載檢查通過后,JVM將會為新生的對象分配內(nèi)存。如果Java堆內(nèi)存是規(guī)整的,內(nèi)存分配采用“指針碰撞”方式;如果內(nèi)存不是規(guī)整的,則采用“空閑列表”的方式。Java堆內(nèi)存是否規(guī)整,取決于使用的垃圾回收器是否帶有壓縮整理的功能。因此,在使用Serial、ParNew等帶Compact過程的收集器時,系統(tǒng)采用的分配算法是指針碰撞,而使用CMS這種基于Mark-Sweep算法的收集器時,通常采用空閑列表。給對象分配內(nèi)線的過程,是指針移動的過程,它不是線程安全的,需要同步;為了解決這個問題,JVM給每個線程在Java堆中預(yù)先分配一塊內(nèi)存,稱為本地線程分配緩沖(Thread Local Allocation Buffer,TLAB),這樣以來,只有緩沖區(qū)用完了,重新分配時才需要同步操作。

  3. 對象內(nèi)存分配完畢之后,JVM把分配的內(nèi)存空間都初始化為零值。

  4. JVM對對象做必要的設(shè)置。例如對象是哪個類的實例、如何找到類的元數(shù)據(jù)、對象的哈希碼、對象的GC分代年齡等,這些信息存放在對象頭(Object Header)中。

  5. 至此,在JVM看來對象創(chuàng)建完成;接下來執(zhí)行<init>方法,把對象按照程序員的意愿初始化,形成一個真正可用的對象。

2.2 對象的內(nèi)存布局

對象在堆中的布局分為三個區(qū)域:對象頭,實例數(shù)據(jù),對齊填充。

  • 對象頭 包括兩個部分,第一部分是“Mark Word”,用于存儲對象自身的運(yùn)行時數(shù)據(jù),包括HashCode、GC分代年齡、鎖狀態(tài)標(biāo)志、線程持有的鎖、偏向ID、偏向時間戳等;第二部分是類型指針,指向存放指向方法區(qū)的類數(shù)據(jù),即JVM通過這個指針來確定對象是哪個類的實例。

  • 實例數(shù)據(jù) 存放類的屬性,包括父類的屬性信息。相同寬度的字段(例如long和double都是8字節(jié))分配在一起。

  • 對齊填充 這是虛擬機(jī)要求對象起始地址必須是8字節(jié)的整數(shù)倍,如果實例數(shù)據(jù)部分不是8字節(jié)的整數(shù)倍,那么就需要對齊填充來補(bǔ)齊,除此之外,并無它意。

2.3 對象的訪問定位

引用存放在Java棧上,數(shù)據(jù)類型為reference;對象存放在Java堆中,引用是如何指向?qū)ο髮嵗兀?/p>

目前主流的訪問方式有兩種,1.使用句柄;2.使用直接指針。

如果使用句柄訪問,那么Java堆中將會分出一塊內(nèi)存作為句柄池,reference中存儲的就是對象的句柄地址,句柄中包含了對象實例數(shù)據(jù)和類型數(shù)據(jù)的具體地址。句柄的好處在于,當(dāng)對象被移動時(垃圾回收時發(fā)生),只會改變句柄中的實例數(shù)據(jù)指針,reference本身不需要修改。

對象句柄訪問.png

如果使用直接指針訪問,reference引用直接指向堆中的對象實例,對象實例的對象頭存放對象類型指針,這種方式的好處在于,減少了一次指針定位的開銷,訪問速度更快。

對象指針訪問.png

HotSpot虛擬機(jī)中使用的是直接指針訪問的方式。

(完)

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

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