運行時數(shù)據(jù)區(qū)(Runtime Data Area)

一、概述

JVM(Java Virtual Machine/Java 虛擬機)在執(zhí)行 Java 程序的過程中會把它所管理的內(nèi)存劃分為若干個不同的數(shù)據(jù)區(qū)域呵恢。這些區(qū)域有各自的用途鞠值,以及創(chuàng)建和銷毀的時間,有的區(qū)域隨著虛擬機進程的啟動而一直存在渗钉,有的區(qū)域則是依賴用戶線程的啟動和結(jié)束而建立和銷毀彤恶。根據(jù)《Java虛擬機規(guī)范》的規(guī)定,Java 虛擬機所管理的內(nèi)存包括五個運行時數(shù)據(jù)區(qū):

各部分特點:

二鳄橘、程序計數(shù)器【Program Counter Register】

程序計數(shù)器是一塊小到可以忽略不計的內(nèi)存空間声离,它可以看作是當前線程所執(zhí)行的字節(jié)碼的行號指示器。在 JVM 的概念模型里瘫怜,字節(jié)碼解釋器工作時就是通過改變該計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令术徊,它是程序控制流的指示器,分支鲸湃、循環(huán)赠涮、跳轉(zhuǎn)、異常處理暗挑、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個計數(shù)器來完成笋除。
由于 JVM 的多線程是通過線程輪流切換、分配處理器執(zhí)行時間的方式來實現(xiàn)的炸裆,在任何一個確定的時刻垃它,一個處理器(對于多核處理器來說是一個內(nèi)核)都只會執(zhí)行一條線程中的指令。因此烹看,為了線程切換后能恢復(fù)到正確的執(zhí)行位置国拇,每條線程都需要有一個獨立的程序計數(shù)器,各條線程之間計數(shù)器互不影響听系,獨立存儲贝奇,這類內(nèi)存區(qū)域為“線程私有”的內(nèi)存。
如果線程正在執(zhí)行的是一個 Java 方法靠胜,該計數(shù)器記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址掉瞳;如果正在執(zhí)行的是本地(Native)方法,該計數(shù)器值則為空(Undefined)浪漠。此內(nèi)存區(qū)域是唯 一一個在《Java虛擬機規(guī)范》中沒有規(guī)定任何 OutOfMemoryError 情況的區(qū)域陕习,因為該區(qū)域中存儲的數(shù)據(jù)所占空間的大小不會隨程序的執(zhí)行而發(fā)生改變。

1??作用

  1. 字節(jié)碼解釋器通過改變程序計數(shù)器來依次讀取指令址愿,從而實現(xiàn)代碼的流程控制该镣。如:順序執(zhí)行、選擇响谓、循環(huán)损合、異常處理省艳。
  2. 在多線程的情況下,程序計數(shù)器用于記錄當前線程執(zhí)行的位置嫁审,從而當線程被切換回來的時候能夠知道該線程上次運行到哪兒了跋炕。

2??特點

  1. 是一塊較小的存儲空間
  2. 線程私有。每條線程都有一個程序計數(shù)器律适。
  3. 是唯一一個不會出現(xiàn) OutOfMemoryError 的內(nèi)存區(qū)域。
  4. 生命周期:隨著線程的創(chuàng)建而創(chuàng)建捂贿,隨著線程的結(jié)束而死亡纠修。

三厂僧、虛擬機椀抡伲【VM Stack】

1??與程序計數(shù)器一樣,Java 虛擬機棧也是線程私有的蕴坪,它的生命周期與線程相同背传。

2??虛擬機棧描述的是 Java 方法執(zhí)行的線程內(nèi)存模型:每個方法被執(zhí)行的時候,JVM 都會同步創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表台夺、操作數(shù)棧径玖、動態(tài)連接、方法出口等信息颤介。每一個方法被調(diào)用直至執(zhí)行完畢的過程梳星,就對應(yīng)著一個棧幀在虛擬機棧中從入棧到出棧的過程。

3??常有人把 Java 內(nèi)存區(qū)域籠統(tǒng)地劃分為堆內(nèi)存(Heap)和棧內(nèi)存(Stack)滚朵,這種劃分方式直接繼承自傳統(tǒng)的 C冤灾、C++ 程序的內(nèi)存布局結(jié)構(gòu),在 Java 里就顯得有些粗糙了辕近,實際的內(nèi)存區(qū)域劃分要比這更復(fù)雜韵吨。不過這種劃分方式的流行也間接說明了程序員最關(guān)注的、與對象內(nèi)存分配關(guān)系最密切的區(qū)域是“堆”和“椧普”兩塊归粉〈涣疲“棧”通常就是指這里講的虛擬機棧糠悼,或者更多的情況下只是指虛擬機棧中局部變量表部分变丧。

4??局部變量表存放了編譯期可知的各種 JVM 基本數(shù)據(jù)類型(boolean、byte绢掰、char痒蓬、short、int滴劲、 float攻晒、long、double)班挖、對象引用(reference 類型鲁捏,它并不等同于對象本身,可能是一個指向?qū)ο笃鹗嫉刂返囊弥羔樝糗剑部赡苁侵赶蛞粋€代表對象的句柄或者其他與此對象相關(guān)的位置)和 returnAddress 類型(指向了一條字節(jié)碼指令的地址)给梅。
這些數(shù)據(jù)類型在局部變量表中的存儲空間以局部變量槽(Slot)來表示,其中 64 位長度的 long 和 double 類型的數(shù)據(jù)會占用兩個變量槽双揪,其余的數(shù)據(jù)類型只占用一個动羽。局部變量表所需的內(nèi)存空間在編譯期間完成分配,當進入一個方法時渔期,這個方法需要在棧幀中分配多大的局部變量空間是完全確定的运吓,在方法運行期間不會改變局部變量表的大小。這里的“大小”是指變量槽的數(shù)量疯趟, 虛擬機真正使用多大的內(nèi)存空間(譬如按照 1 個變量槽占用 32 個比特/bit拘哨、64 個比特/bit,或者更多)來實現(xiàn)一個變量槽信峻,這是完全由具體的虛擬機實現(xiàn)自行決定的事情倦青。

5??在《Java虛擬機規(guī)范》中,對這個內(nèi)存區(qū)域規(guī)定了兩類異常狀況:

  1. 如果虛擬機棧的內(nèi)存大小不允許動態(tài)擴展盹舞,那么當線程請求的棧深度大于當前虛擬機棧所允許的深度产镐,將拋出 StackOverflowError
  2. 如果 Java 虛擬機棧容量可以動態(tài)擴展矾策,當棧擴展時無法申請到足夠的內(nèi)存會拋出 OutOfMemoryError磷账。

6??注意:

  1. 局部變量表的創(chuàng)建是在方法被執(zhí)行的時候,隨著棧幀的創(chuàng)建而創(chuàng)建贾虽。而且逃糟,局部變量表的大小在編譯時期就確定下來了,在創(chuàng)建的時候只需分配事先規(guī)定好的大小即可。此外绰咽,在方法運行的過程中局部變量表的大小是不會發(fā)生改變的菇肃。

  2. StackOverFlowError 和 OutOfMemoryError 的區(qū)別:
    StackOverFlowError 表示當前線程申請的棧超過了事先定好的棧的最大深度,但內(nèi)存空間可能還有很多取募。
    而 OutOfMemoryError 是指當線程申請棧時發(fā)現(xiàn)棧已經(jīng)滿了琐谤,而且內(nèi)存也全都用光了。

四玩敏、本地方法椂芳桑【Native Method Stacks】

1??本地方法棧與虛擬機棧所發(fā)揮的作用是非常相似的,其區(qū)別只是虛擬機棧為虛擬機執(zhí)行 Java 方法(也就是字節(jié)碼)服務(wù)旺聚,而本地方法棧則是為虛擬機使用到的本地(Native)方法服務(wù)织阳。本地方法被執(zhí)行的時候,在本地方法棧也會創(chuàng)建一個棧幀砰粹,用于存放該本地方法的局部變量表唧躲、操作數(shù)棧、動態(tài)鏈接碱璃、出口信息弄痹。方法執(zhí)行完畢后相應(yīng)的棧幀也會出棧并釋放內(nèi)存空間。

2??《Java虛擬機規(guī)范》對本地方法棧中方法使用的語言嵌器、使用方式與數(shù)據(jù)結(jié)構(gòu)并沒有任何強制規(guī)定肛真,因此具體的虛擬機可以根據(jù)需要自由實現(xiàn)它,甚至有的 JVM (譬如 Hot-Spot 虛擬機)直接就把本地方法棧和虛擬機棧合二為一嘴秸。與虛擬機棧一樣毁欣,本地方法棧也會在棧深度溢出或者棧擴展失敗時分別拋出 StackOverflowError 和 OutOfMemoryError庇谆。

五岳掐、堆【Heap】

1??Java 堆是虛擬機所管理的內(nèi)存中最大的一塊。Java 堆是被所有線程共享的一塊內(nèi)存區(qū)域饭耳,在虛擬機啟動時創(chuàng)建串述。此內(nèi)存區(qū)域的唯一目的就是存放對象實例,Java 里所有的對象實例都在這里分配內(nèi)存寞肖。

在《Java虛擬機規(guī)范》中對 Java 堆的描述是:“The heap is the runtime data area from which memory for all class instances and arrays is allocated纲酗。”所有的對象實例以及數(shù)組都應(yīng)當在堆上分配新蟆。

2??Java 堆是垃圾收集器管理的內(nèi)存區(qū)域觅赊。從回收內(nèi)存的角度看,現(xiàn)代垃圾收集器大部分都是基于分代新生代【Eden琼稻、From Survior吮螺、To Survior】、老年代收集理論設(shè)計的,但這不是某個 JVM 具體實現(xiàn)的固有內(nèi)存布局鸠补,更不是《Java虛擬機規(guī)范》里對 Java 堆的進一步細致劃分萝风。

3??如果從分配內(nèi)存的角度看,所有線程共享的 Java 堆中可以劃分出多個線程私有的分配緩沖區(qū) (Thread Local Allocation Buffer紫岩,TLAB)规惰,以提升對象分配時的效率。不過無論從什么角度泉蝌,無論如何劃分歇万,都不會改變 Java 堆中存儲內(nèi)容的共性,無論是哪個區(qū)域勋陪,存儲的都只能是對象的實例堕花,將 Java 堆細分的目的只是為了更好地回收內(nèi)存,或者更快地分配內(nèi)存粥鞋。

根據(jù)《Java虛擬機規(guī)范》的規(guī)定缘挽,Java 堆可以處于物理上不連續(xù)的內(nèi)存空間中,但在邏輯上它應(yīng)該被視為連續(xù)的呻粹,這點就像用磁盤空間去存儲文件一樣壕曼,并不要求每個文件都連續(xù)存放。但對于大對象(典型的如數(shù)組對象)等浊,多數(shù)虛擬機出于實現(xiàn)簡單腮郊、存儲高效的考慮,很可能會要求連續(xù)的內(nèi)存空間筹燕。

4??Java 堆既可以被實現(xiàn)成固定大小的轧飞,也可以是可擴展的,當前主流的 JVM 都是按照可擴展來實現(xiàn)的(通過參數(shù)-Xmx和-Xms設(shè)定)撒踪。如果在 Java 堆中沒有內(nèi)存完成實例分配过咬,并且堆也無法再擴展時,JVM 將會拋出 OutOfMemoryError制妄。

六掸绞、方法區(qū)【Method Area】--非堆(Non-Heap)

1??《Java虛擬機規(guī)范》中把方法區(qū)描述為堆的一個邏輯部分,它與 Java 堆一樣耕捞,是各個線程共享的內(nèi)存區(qū)域衔掸,用于存儲已被虛擬機加載的類型信息、常量俺抽、靜態(tài)變量敞映、即時編譯器編譯后的代碼緩存等數(shù)據(jù)。

2??《Java虛擬機規(guī)范》對方法區(qū)的約束非常寬松磷斧,除了和 Java 堆一樣不需要連續(xù)的內(nèi)存和可以選擇固定大小或者可擴展外振愿,甚至還可以選擇不實現(xiàn)垃圾收集诗芜。這區(qū)域的內(nèi)存回收目標主要是針對常量池的回收和對類型的卸載。內(nèi)存回收效率低埃疫,回收一遍內(nèi)存之后可能只有少量信息無效伏恐。

3??根據(jù)《Java虛擬機規(guī)范》的規(guī)定,如果方法區(qū)無法滿足新的內(nèi)存分配需求時栓霜,將拋出 OutOfMemoryError翠桦。

4??運行時常量池【Runtime Constant Pool】

  1. 運行時常量池是方法區(qū)的一部分。Class 文件中除了有類的版本胳蛮、字段销凑、方法、接口等描述信息外仅炊,還有一項信息是常量池表(Constant Pool Table)斗幼,用于存放編譯期生成的各種字面量與符號引用,這部分內(nèi)容將在類加載后存放到方法區(qū)的運行時常量池中抚垄。

  2. JVM 對于 Class 文件每一部分(自然也包括常量池)的格式都有嚴格規(guī)定蜕窿,如每一個字節(jié)用于存儲哪種數(shù)據(jù)都必須符合規(guī)范上的要求才會被虛擬機認可、加載和執(zhí)行呆馁。但對于運行時常量池桐经,《Java虛擬機規(guī)范》并沒有做任何細節(jié)的要求,不同提供商實現(xiàn)的虛擬機可以按照自己的需要來實現(xiàn)這個內(nèi)存區(qū)域浙滤,不過一般來說阴挣,除了保存 Class 文件中描述的符號引用外,還會把由符號引用翻譯出來的直接引用也存儲在運行時常量池中纺腊。

  3. 運行時常量池相對于 Class 文件常量池的另外一個重要特征是具備動態(tài)性畔咧,Java 并不要求常量一定只有編譯期才能產(chǎn)生,也就是說揖膜,并非只有預(yù)置入 Class 文件中常量池的內(nèi)容才能進入方法區(qū)運行時常量池誓沸,運行期間也可以將新的常量放入池中,這種特性被開發(fā)人員利用得比較多的便是 String 類的 intern()次氨,它就能在運行期間向常量池中添加字符串常量蔽介。當運行時常量池中的某些常量沒有被對象引用,同時也沒有被變量引用煮寡,那么就需要垃圾收集器回收。

  4. 既然運行時常量池是方法區(qū)的一部分犀呼,自然受到方法區(qū)內(nèi)存的限制幸撕,當常量池?zé)o法再申請到內(nèi)存時會拋出 OutOfMemoryError。

注:
一般在一個類中通過public static final來聲明一個常量外臂,當該類被 JVM 加載后坐儿,Class 文件中的常量就存放在方法區(qū)的運行時常量池中。而且在運行期間,可以向常量池中添加新的常量貌矿。如同 String 類的 intern()炭菌。

七、直接內(nèi)存【Direct Memory】

1??直接內(nèi)存并不是虛擬機運行時數(shù)據(jù)區(qū)的一部分逛漫,也不是《Java虛擬機規(guī)范》中定義的內(nèi)存區(qū)域黑低。但是這部分內(nèi)存也被頻繁地使用,而且也可能導(dǎo)致 OutOfMemoryError 出現(xiàn)酌毡。

2??在 JDK4 中新加入了 NIO(New Input/Output) 類克握,引入了一種基于通道(Channel)與緩沖區(qū)(Buffer)的 I/O 方式,它可以使用 Native 方法庫直接分配堆外內(nèi)存枷踏,然后通過一個存儲在 Java 堆里面的 DirectByteBuffer 對象作為這塊內(nèi)存的引用進行操作菩暗。這樣能在一些場景中顯著提高性能,因為避免了在 Java 堆和 Native 堆中來回復(fù)制數(shù)據(jù)旭蠕。

3??顯然停团,本機直接內(nèi)存的分配不會受到 Java 堆大小的限制,但是掏熬,既然是內(nèi)存客蹋,則肯定還是會受到本機總內(nèi)存(包括物理內(nèi)存、SWAP 分區(qū)或者分頁文件)大小以及處理器尋址空間的限制孽江,一般服務(wù)器管理員配置虛擬機參數(shù)時讶坯,會根據(jù)實際內(nèi)存去設(shè)置 -Xmx 等參數(shù)信息,但經(jīng)常忽略掉直接內(nèi)存岗屏,使得各個內(nèi)存區(qū)域總和大于物理內(nèi)存限制(包括物理的和操作系統(tǒng)級的限制)辆琅,從而導(dǎo)致動態(tài)擴展時出現(xiàn)
OutOfMemoryError 異常。

八这刷、總結(jié)

  1. JVM 的內(nèi)存模型中一共有兩個“椡裱蹋”,分別是:虛擬機棧和本地方法棧暇屋。兩個“椝圃”的功能類似,都是方法運行過程的內(nèi)存模型咐刨。并且兩個“楆夹疲”內(nèi)部構(gòu)造相同,都是線程私有定鸟。
    只不過虛擬機棧描述的是 Java 方法運行過程的內(nèi)存模型而涉;而本地方法棧是描述 Java 本地(Native)方法運行過程的內(nèi)存模型。
  2. JVM 的內(nèi)存模型中一共有兩個“堆”联予,一個是原本的堆啼县,一個是方法區(qū)材原。方法區(qū)本質(zhì)上是屬于堆的一個邏輯部分。堆中存放對象季眷,方法區(qū)中存放類信息余蟹、常量、靜態(tài)變量子刮、即時編譯器編譯的代碼威酒。
  3. 堆是 JVM 中最大的一塊內(nèi)存區(qū)域,也是垃圾收集器主要的工作區(qū)域话告。
  4. 程序計數(shù)器兼搏、虛擬機棧、本地方法棧是線程私有的沙郭。并且它們的生命周期和所屬的線程一樣佛呻。
    而堆、方法區(qū)是線程共享的病线,在 JVM 中只有一個堆吓著、一個方法棧。并且在 JVM 啟動的時候就創(chuàng)建送挑,JVM 停止才銷毀绑莺。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市惕耕,隨后出現(xiàn)的幾起案子纺裁,更是在濱河造成了極大的恐慌,老刑警劉巖司澎,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件欺缘,死亡現(xiàn)場離奇詭異,居然都是意外死亡挤安,警方通過查閱死者的電腦和手機谚殊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蛤铜,“玉大人嫩絮,你說我怎么就攤上這事∥Х剩” “怎么了剿干?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長虐先。 經(jīng)常有香客問我怨愤,道長,這世上最難降的妖魔是什么蛹批? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任撰洗,我火速辦了婚禮,結(jié)果婚禮上腐芍,老公的妹妹穿的比我還像新娘差导。我一直安慰自己,他們只是感情好猪勇,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布设褐。 她就那樣靜靜地躺著,像睡著了一般泣刹。 火紅的嫁衣襯著肌膚如雪助析。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天椅您,我揣著相機與錄音外冀,去河邊找鬼。 笑死掀泳,一個胖子當著我的面吹牛雪隧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播员舵,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼脑沿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了马僻?” 一聲冷哼從身側(cè)響起庄拇,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎韭邓,沒想到半個月后措近,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡仍秤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年熄诡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诗力。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡凰浮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出苇本,到底是詐尸還是另有隱情袜茧,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布瓣窄,位于F島的核電站笛厦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏俺夕。R本人自食惡果不足惜裳凸,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一贱鄙、第九天 我趴在偏房一處隱蔽的房頂上張望其做。 院中可真熱鬧绎晃,春花似錦顶掉、人聲如沸迷守。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坐搔。三九已至何恶,卻和暖如春捌议,著一層夾襖步出監(jiān)牢的瞬間哼拔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工瓣颅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留倦逐,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓弄捕,卻偏偏與公主長得像僻孝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子守谓,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

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