深入理解Java虛擬機(jī)_1(Java內(nèi)存區(qū)域與內(nèi)存溢出異常)

Cerated by westfallon on 8/19

運(yùn)行時(shí)數(shù)據(jù)區(qū)域

程序技術(shù)器

  • 程序計(jì)數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間刑顺,它可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器
  • 在虛擬機(jī)的概念模型里,字節(jié)碼解釋器工作時(shí)就是通過(guò)改變這個(gè)計(jì)數(shù)器的值來(lái)選取下一條需要執(zhí)行的字節(jié)碼指令远剩,分支、循環(huán)痴腌、跳轉(zhuǎn)焰坪、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個(gè)計(jì)數(shù)器來(lái)完成
  • 為了線程切換后能恢復(fù)到正確的執(zhí)行位置阁猜,每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器丸逸,各條線程之間計(jì)數(shù)器互不影響,獨(dú)立儲(chǔ)存剃袍,我們稱這類內(nèi)存區(qū)域?yàn)椤熬€程私有”的內(nèi)存
  • 如果線程正在執(zhí)行的是一個(gè)Java方法黄刚,這個(gè)計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址
  • 如果正在執(zhí)行的是Native方法,這個(gè)計(jì)數(shù)器值則為空(Undefined)
  • 此內(nèi)存區(qū)域是唯一一個(gè)在Java虛擬機(jī)規(guī)范中沒(méi)有規(guī)定任何OutOfMemoryError情況的區(qū)域

Java虛擬機(jī)棧

  • Java虛擬機(jī)棧(Java Virtual Machines Stacks)也是線程私有的民效,它的生命周期與線程相同
  • Java虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame)用于儲(chǔ)存局部變量表憔维、操作數(shù)棧、動(dòng)態(tài)鏈接畏邢、方法出口等信息埋同。每一個(gè)方法從調(diào)用直至執(zhí)行完成的過(guò)程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過(guò)程
  • 經(jīng)常有人將Java內(nèi)存區(qū)分為堆內(nèi)存(Heap)和棧內(nèi)存(Stack)棵红,實(shí)際上遠(yuǎn)比這復(fù)雜凶赁,只不過(guò)大多數(shù)程序員最關(guān)注這兩部分,其中的棧就是這里的虛擬機(jī)棧逆甜,或者說(shuō)是虛擬機(jī)棧中的局部變量表部分
  • 局部變量表存放了編譯器可知的各種基本數(shù)據(jù)類型虱肄、對(duì)象引用(reference類型)和returnAddress類型(指向了一條字節(jié)碼指令的地址)
  • 局部變量表所需的內(nèi)存空間在編譯器完成分配,當(dāng)進(jìn)入一個(gè)方法時(shí)交煞,這個(gè)方法需要在幀中分配多大的局部變量空間是完全確定的咏窿,在方法運(yùn)行期間不會(huì)改變局部變量表的大小
  • Java虛擬機(jī)規(guī)范中對(duì)這個(gè)區(qū)域規(guī)定了兩種異常狀況:
    • 如果線程請(qǐng)求棧深度大于虛擬機(jī)所允許的深度,將拋出StackOverflowError異常
    • 如果虛擬機(jī)可以動(dòng)態(tài)擴(kuò)展素征,如果擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存集嵌,就會(huì)拋出OutOfMemoryError異常

本地方法棧

  • 本地方法棧(Native Method Stack)與虛擬機(jī)棧所發(fā)揮的作用是非常相似的,它們之間的區(qū)別不過(guò)是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法服務(wù)御毅,而本地方法棧則為虛擬機(jī)使用到的Native方法服務(wù)
  • 與虛擬機(jī)棧一樣根欧,本地方法棧也會(huì)拋出StackOverflowErrorOutOdMemoryError異常

Java堆

  • 對(duì)大多數(shù)應(yīng)用來(lái)說(shuō),Java堆(Java Heap)是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊
  • Java堆是被所有線程共享的一塊內(nèi)存區(qū)域端蛆,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建
  • 此內(nèi)存的唯一目的就是存放對(duì)象實(shí)例凤粗,幾乎所有的對(duì)象實(shí)例都在這里分配內(nèi)存
  • Java堆是垃圾收集器管理的主要區(qū)域,因此很多時(shí)候也被稱為“GC堆”(Garbage Collected Heap)
  • Java堆可以處于物理上不連續(xù)的內(nèi)存空間中今豆,只要邏輯上是連續(xù)的即可
  • 如果在堆中沒(méi)有內(nèi)存完成實(shí)例分配嫌拣,并且堆也無(wú)法擴(kuò)展時(shí)柔袁,將會(huì)拋出OutOfMemoryError異常

方法區(qū)

  • 方法區(qū)(Method Area)與Java堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域异逐,它用于儲(chǔ)存已被虛擬機(jī)加載的類信息捶索、常量、靜態(tài)變量灰瞻、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)
  • 當(dāng)方法區(qū)無(wú)法滿足內(nèi)存分配需求時(shí)情组,將拋出OutOfMemoryError異常

運(yùn)行時(shí)常量池

  • 運(yùn)行時(shí)常量池(Runtime Constant Pool)是方法區(qū)的一部分,存放編譯期生成的各種字面量和符號(hào)引用
  • 運(yùn)行時(shí)常量池相對(duì)于Class文件常量池的另一個(gè)重要特征是具備動(dòng)態(tài)性箩祥,Java語(yǔ)言并不要求常量一定只有編譯期才能產(chǎn)生院崇,也就是并非預(yù)置于Class文件中常量池的內(nèi)容才能進(jìn)入方法區(qū)運(yùn)行時(shí)常量池,運(yùn)行期間也可能將新的常量放入池中袍祖,這種特性被利用較多的是String類的intern()方法
  • 當(dāng)常量池?zé)o法再申請(qǐng)到內(nèi)存時(shí)會(huì)拋出OutOfMemoryError異常

直接內(nèi)存

  • 直接內(nèi)存(Direct Memory)并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分底瓣,也不是Java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域,但是這部分內(nèi)存被頻繁使用蕉陋,也有可能導(dǎo)致OutOfMemoryError異常
  • NIO(New Input/Output)類引入了一種基于通道(Channel)與緩沖區(qū)(Buffer)的I/O方式捐凭,它可以用Native函數(shù)庫(kù)直接分配堆外內(nèi)存,然后通過(guò)一個(gè)儲(chǔ)存在Java堆中的DirectByteBuffer對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作凳鬓,這樣能在一些場(chǎng)景中顯著提高性能茁肠,因?yàn)楸苊饬嗽贘ava堆和Native堆中來(lái)回復(fù)制數(shù)據(jù)

HotSpot虛擬機(jī)

對(duì)象的創(chuàng)建

  • 首先將去檢查這個(gè)指令的參數(shù)能否在常量池中定位到一個(gè)類的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類是否已被加載缩举、解析和初始化過(guò)垦梆。如果沒(méi)有,那必須先執(zhí)行相應(yīng)的類加載過(guò)程
  • 接下來(lái)虛擬機(jī)將為新生對(duì)象分配內(nèi)存仅孩。對(duì)象所需內(nèi)存的大小在類加載完成后便可確定托猩,為對(duì)象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從堆中劃分出來(lái)
    • 如果堆是絕對(duì)規(guī)整的,虛擬機(jī)使用一個(gè)指針劃分使用過(guò)的和沒(méi)使用過(guò)的內(nèi)存辽慕,這種方式稱為“指針碰撞”(Bump the Pointer)
    • 如果堆的內(nèi)存不是規(guī)整的京腥,虛擬機(jī)就必須維護(hù)一個(gè)列表,記錄那些內(nèi)存塊是可用的溅蛉,分配時(shí)從列表中找到一塊足夠大的空間劃分給對(duì)象公浪,并更新列表上的記錄,這種方式稱為“空閑列表”(Free List)
    • 解決創(chuàng)建對(duì)象時(shí)并發(fā)情況下線程安全問(wèn)題:
      • 一種是對(duì)分配內(nèi)存空間的動(dòng)作進(jìn)行同步處理
      • 另一種是把內(nèi)存分配的動(dòng)作按照線程劃分在不同的空間之中進(jìn)行船侧,即每個(gè)內(nèi)存在Java堆中預(yù)先分配一小塊內(nèi)存欠气,稱為本地線程分配緩沖(Thread Local Allocation Buffer,TLAB)勺爱。哪個(gè)線程要分配內(nèi)存晃琳,就在哪個(gè)線程的TLAB上分配,只有TLAB用完并且分配新的TLAB時(shí)琐鲁,才需要同步鎖定
  • 內(nèi)存分配完成后卫旱,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零值(不包括對(duì)象頭),如果使用TLAB围段,這一工作過(guò)程也可以提前至TLAB分配時(shí)進(jìn)行顾翼,這一步保證了對(duì)象的實(shí)例字段在Java代碼中可以不賦初始值直接使用,程序能訪問(wèn)到這些字段的數(shù)據(jù)類型所對(duì)應(yīng)的零值
  • 接下來(lái)奈泪,虛擬機(jī)要對(duì)對(duì)象進(jìn)行必要的設(shè)置适贸。例如這個(gè)對(duì)象是哪個(gè)類的實(shí)例、如何才能找到類的元數(shù)據(jù)信息涝桅、對(duì)象的哈希碼拜姿、對(duì)象的GC分代年齡等信息。這些信息存放在對(duì)象的對(duì)象頭(Object Header)中
  • 以上工作完成后冯遂,從虛擬機(jī)的角度來(lái)看對(duì)象已經(jīng)創(chuàng)建完成蕊肥,姐先來(lái)將執(zhí)行init方法

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

  • 對(duì)象在內(nèi)存中儲(chǔ)存的布局可以分為三塊區(qū)域:對(duì)象頭(Header)、實(shí)例數(shù)據(jù)(Instance Data)和對(duì)象填充(Padding)
  • 對(duì)象頭包括兩部分信息:
    • 第一部分用于儲(chǔ)存對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù)蛤肌,如哈希碼(HashCode)壁却、GC分代年齡、鎖狀態(tài)標(biāo)志裸准、線程持有的鎖展东、偏向線程ID、偏向時(shí)間戳等
    • 另一部分是類型指針炒俱,即對(duì)象指向它的類元數(shù)據(jù)的指針盐肃,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類的實(shí)例。另外权悟,如果對(duì)象是一個(gè)Java數(shù)組恼蓬,那在對(duì)象頭中還必須有一塊用于記錄數(shù)組長(zhǎng)度的數(shù)據(jù)
  • 實(shí)例數(shù)據(jù)部分是對(duì)象真正儲(chǔ)存的有效信息,也是在程序代碼中所定義的各種類型的字段內(nèi)容僵芹。這部分儲(chǔ)存順序會(huì)受到虛擬機(jī)分配策略參數(shù)(FieldsAllocationStyle)和字段在Java源碼中定義順序的影響
  • 第三部分對(duì)其填充不是必須存在的处硬,也沒(méi)有特別的含義,它僅僅起著占位符的作用拇派,因?yàn)镠otSpot VM的自動(dòng)內(nèi)存管理系統(tǒng)要求對(duì)象起始地址必須是8字節(jié)的整數(shù)倍荷辕,即對(duì)象的大小必須是8字節(jié)的整數(shù)倍

對(duì)象的訪問(wèn)定位

  • Java程序需要通過(guò)棧上的reference數(shù)據(jù)來(lái)操作堆上的具體對(duì)象
  • 對(duì)象訪問(wèn)方式是取決于虛擬機(jī)實(shí)現(xiàn)而定的,目前主流的訪問(wèn)方式有使用句柄和直接指針兩種
    • 如果使用句柄訪問(wèn)的話件豌,Java堆中將會(huì)劃分出一塊內(nèi)存來(lái)作為句柄池疮方,reference中儲(chǔ)存的就是對(duì)象的句柄地址,而句柄中包含了對(duì)象實(shí)例數(shù)據(jù)與類型數(shù)據(jù)各自的具體地址信息
    • 如果使用直接指針訪問(wèn)茧彤,那么Java堆對(duì)象的布局中就必須考慮如何放置訪問(wèn)類型數(shù)據(jù)的相關(guān)信息骡显,而reference對(duì)象中儲(chǔ)存的直接就是對(duì)象地址
  • 這兩種方式各有優(yōu)劣
    • 使用句柄來(lái)訪問(wèn)的最大好處就是reference中儲(chǔ)存的是穩(wěn)定的句柄地址,在對(duì)象被移動(dòng)(垃圾收集時(shí)移動(dòng)對(duì)象是非常普遍的行為)時(shí)只會(huì)改變句柄中的實(shí)例數(shù)據(jù)指針,而reference本身不會(huì)改變
    • 使用直接指針訪問(wèn)方式的最大好處就是速度更快惫谤,它節(jié)省了一次指針定位的時(shí)間開(kāi)銷(xiāo)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末壁顶,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子溜歪,更是在濱河造成了極大的恐慌若专,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蝴猪,死亡現(xiàn)場(chǎng)離奇詭異调衰,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)自阱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)嚎莉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人沛豌,你說(shuō)我怎么就攤上這事趋箩。” “怎么了琼懊?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵阁簸,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我哼丈,道長(zhǎng)启妹,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任醉旦,我火速辦了婚禮饶米,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘车胡。我一直安慰自己檬输,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布匈棘。 她就那樣靜靜地躺著丧慈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪主卫。 梳的紋絲不亂的頭發(fā)上逃默,一...
    開(kāi)封第一講書(shū)人閱讀 51,482評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音簇搅,去河邊找鬼完域。 笑死,一個(gè)胖子當(dāng)著我的面吹牛瘩将,可吹牛的內(nèi)容都是我干的吟税。 我是一名探鬼主播凹耙,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼肠仪!你這毒婦竟也來(lái)了肖抱?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤藤韵,失蹤者是張志新(化名)和其女友劉穎虐沥,沒(méi)想到半個(gè)月后熊经,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體泽艘,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年镐依,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了匹涮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡槐壳,死狀恐怖然低,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情务唐,我是刑警寧澤雳攘,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站枫笛,受9級(jí)特大地震影響吨灭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜刑巧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一喧兄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧啊楚,春花似錦吠冤、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至颜价,卻和暖如春涯保,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拍嵌。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工遭赂, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人横辆。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓撇他,卻偏偏與公主長(zhǎng)得像茄猫,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子困肩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354