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

剛工作的時候岁疼,Leader跟我說:你現(xiàn)在只知道寫業(yè)務(wù)代碼阔涉,但不了解自己的JVM運行情況,怎么去調(diào)優(yōu),怎么去排查故障瑰排。只有真正了解掌控JVM贯要,才能稱為一個真正的Java專家⊥肿。《深入理解Java虛擬機(jī)》這本書崇渗,是介紹JVM知識的一本十分難得的書,值得去反復(fù)閱讀京郑、揣摩宅广、反思。這個系列的文章傻挂,是我在閱讀這本書的過程中的點滴筆記乘碑,如果喜歡,請更多支持原書作者出版的圖書金拒。同時兽肤,如果喜歡本文,請給與多多支持绪抛。

1.概述

對于C资铡、C++開發(fā)人員來說,在內(nèi)存管理方面幢码,擁有對每個對象的絕對控制能力笤休,從對象的產(chǎn)生到終結(jié),承擔(dān)著全部責(zé)任症副。

對于Java開發(fā)人員來說店雅,在虛擬機(jī)的幫助下,不需要為每個對象去寫刪除釋放方法贞铣,不容易出現(xiàn)內(nèi)存泄露和內(nèi)存溢出的問題闹啦。不過,正是由于太過依賴于JVM辕坝,一旦出現(xiàn)內(nèi)存泄露和內(nèi)存溢出的問題窍奋,排查錯誤將會是一件很難的工作。

2.運行時數(shù)據(jù)區(qū)域

Java虛擬機(jī)在執(zhí)行Java程序的過程中會把它管理的內(nèi)存劃為若干個不同的數(shù)據(jù)區(qū)域酱畅。

Java虛擬機(jī)運行時數(shù)據(jù)區(qū)

2.1 程序計數(shù)器

程序計數(shù)器:一塊較小的內(nèi)存空間琳袄,可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器。每條線程都需要有一個獨立的程序計數(shù)器纺酸。

在JVM的概念模型中窖逗,字節(jié)碼解釋器工作時,通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令餐蔬,分支碎紊、循環(huán)在张、跳轉(zhuǎn)、異常處理矮慕、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個計數(shù)器來完成帮匾。

如果線程正在執(zhí)行的是一個Java方法,計數(shù)器記錄的值是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址痴鳄;如果正在執(zhí)行的是一個Native方法瘟斜,計數(shù)器記錄的值為空。

Tip: 此內(nèi)存區(qū)域是唯一一個在Java虛擬機(jī)規(guī)范中沒有規(guī)定任何OutOfMemeoryError情況的區(qū)域痪寻。

2.2 Java虛擬機(jī)棧

Java虛擬機(jī)棧:每個Java方法在執(zhí)行的同時都會創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表螺句、操作數(shù)棧、動態(tài)鏈接橡类、方法出口等信息蛇尚。每個方法從調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個棧幀在虛擬機(jī)棧中入棧到出棧的過程顾画。

經(jīng)常有人把Java內(nèi)存區(qū)分為堆內(nèi)存(Heap)和棧內(nèi)存(Stack)取劫,這種分法比較粗糙。實際上研侣,“椘仔埃”實際就是虛擬機(jī)棧,或者說虛擬機(jī)棧中的局部變量表部分庶诡。

局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型(boolean惦银、byte、char末誓、short扯俱、int、float喇澡、long迅栅、double)、對象引用(一個指向?qū)ο笃鹗嫉刂返囊弥羔樍糜模蛞粋€代表對象的句柄或其他與此對象相關(guān)的位置)和returnAddress類型(指向一條字節(jié)碼指令的地址)库继。

Tip:在Java虛擬機(jī)規(guī)范中箩艺,對這個區(qū)域規(guī)定了兩種異常狀況:
如果線程請求的棧深度大于虛擬機(jī)允許的深度窜醉,則拋出StackOverflowError異常;
如果虛擬機(jī)椧兆唬可動態(tài)擴(kuò)展榨惰,如果擴(kuò)展時無法申請到足夠的內(nèi)存,則拋出OutOfMemoryError異常静汤。

2.3 本地方法棧

本地方法棧:與虛擬機(jī)棧所發(fā)揮的作用非常相似琅催,區(qū)別在于 虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法服務(wù)居凶,而本地方法棧則為虛擬機(jī)使用到的Native方法服務(wù)。與虛擬機(jī)棧一樣藤抡,本地方法棧區(qū)域也會拋出StackOverflowError和OutOfMemoryError異常侠碧。

2.4 Java堆

Java堆:被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動時創(chuàng)建缠黍。此內(nèi)存區(qū)域的唯一目的就是存放對象實例弄兜,幾乎所有對象實例都是在這里分配內(nèi)存。

Java堆是垃圾收集器管理的主要區(qū)域瓷式,因此也成為GC堆替饿。

從內(nèi)存回收角度來看,Java堆可以細(xì)分為:新生代和老年代贸典;再細(xì)致些有Eden空間视卢、From Survivor空間、To Survivor空間等廊驼。

從內(nèi)存分配角度來看据过,線程共享的Java堆可能劃分出多個線程私有的分配緩沖區(qū)(TLAB)。

Tip:如果在堆中沒有內(nèi)存完成實例分配妒挎,并且堆也無法再擴(kuò)展時蝶俱,將會拋出OutOfMemoryError異常〖⒙可以通過-Xmx和-Xms來控制堆大小榨呆。

2.5 方法區(qū)

方法區(qū):線程共享的內(nèi)存區(qū)域,用于存儲已被虛擬機(jī)加載的類信息庸队、常量积蜻、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)彻消。

這個區(qū)域的內(nèi)存回收目標(biāo)主要是針對常量池的回收和對類型的卸載竿拆,一般來說,這個區(qū)域的回收成績比較難以令人滿意宾尚。

根據(jù)Java虛擬機(jī)規(guī)范的規(guī)定丙笋,當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時,將會拋出OutOfMemoryError異常煌贴。

2.6 運行時常量池

運行時常量池:是方法區(qū)的一部分御板。Class文件中除了有類的版本、字段牛郑、方法怠肋、接口等描述信息外,還有一項信息是常量池淹朋,用于存放編譯期生成的各種字面量和符號引用笙各,這部分內(nèi)容將在類加載后進(jìn)入方法區(qū)的運行時常量池中存放钉答。

2.7 直接內(nèi)存

直接內(nèi)存:并不是虛擬機(jī)運行時數(shù)據(jù)區(qū)的一部分,也不是Java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域杈抢。但是這部分內(nèi)存也被頻繁地使用数尿,也可能導(dǎo)致OutOfMemoryError異常出現(xiàn)。

在JDK 1.4 中新加入了NIO類惶楼,引入了一種基于通道(Channel)與緩存區(qū)(Buffer)的I/O方式砌创,可以使用Native函數(shù)庫直接分配堆外內(nèi)存,然后通過一個存儲在Java堆中的DirectByteBuffer對象作為這塊內(nèi)存的引用進(jìn)行操作鲫懒,這樣避免了在Java堆和Native堆中來回復(fù)制數(shù)據(jù)嫩实。

Tip:若忽略直接內(nèi)存的限制,導(dǎo)致各個內(nèi)存區(qū)域總和大于物理內(nèi)存限制窥岩,會導(dǎo)致動態(tài)擴(kuò)展時出現(xiàn)OutOfMemoryError異常甲献。

3.虛擬機(jī)對象探秘

3.1 對象的創(chuàng)建

在語言層面上,創(chuàng)建對象通常僅僅是一個new操作而已颂翼;但是在虛擬機(jī)中晃洒,對象的創(chuàng)建是一個復(fù)雜的過程。

  • A. 虛擬機(jī)遇到一條new指令時朦乏,首先將去檢查這個指令的參數(shù)是否能在常量池中定位到一個類的符號引用球及,并且檢查這個符號引用代表的類是否已被加載、解析和初始化過呻疹。如果沒有吃引,則先執(zhí)行相應(yīng)的類加載過程。

  • B. 在類加載檢查通過后刽锤,接下來虛擬機(jī)將為新生對象分配內(nèi)存镊尺。
    在使用Serial、ParNew等帶Compact過程的收集器時并思,系統(tǒng)采用的分配算法是 指針碰撞庐氮;
    在使用CMS這種基于Mark-Sweep算法的收集器時,通常采用 空閑列表宋彼。

  • C. 內(nèi)存分配完成后弄砍,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零值。

  • D. 虛擬機(jī)要對對象進(jìn)行必要的設(shè)值输涕,例如這個對象是哪個類的實例音婶、如何才能找到類的元數(shù)據(jù)信息、對象的哈希碼占贫、對象的GC分代年齡等信息桃熄。這些信息存放在對象的對象頭之中先口。

  • E. 執(zhí)行new指令之后會接著執(zhí)行<init>方法型奥,把對象按照程序員的意愿進(jìn)行初始化瞳收,這樣一個真正可用的對象才算完全產(chǎn)生出來。

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

在HotSpot虛擬機(jī)中厢汹,對象在內(nèi)存中存儲的布局可以分為3塊區(qū)域:對象頭(Header)螟深、實例數(shù)據(jù)(Instance Data)和對齊填充(Padding)。

  • A. 對象頭:包含兩部分信息烫葬,第一部分用于存儲對象自身的運行時數(shù)據(jù)界弧,如哈希碼、GC分代年齡搭综、鎖狀態(tài)標(biāo)志垢箕、線程持有的鎖、偏移線程ID兑巾、偏向時間戳等条获;另一部分是類型指針,即對象指向它的類元數(shù)據(jù)的指針蒋歌,虛擬機(jī)通過這個指針來確定這個對象是哪個類的實例帅掘。

  • B. 實例數(shù)據(jù):對象真正存儲的有效信息,也是在程序代碼中所定義的各種類型的字段內(nèi)容堂油。這部分的存儲順序會收到虛擬機(jī)分配策略參數(shù)和字段在Java源代碼中定義順序的影響修档。HotSpot虛擬機(jī)默認(rèn)的分配策略為longs/doubles、ints府框、shorts/chars吱窝、bytes/booleans、oops(Ordinary Object Pointers),相同寬度的字段總是被分配到一起迫靖。

  • C. 對齊填充:并不是必然存在癣诱。對象的大小必須是8字節(jié)的整數(shù)倍,當(dāng)對象實例數(shù)據(jù)部分沒有對齊時袜香,就需要通過對齊填充來補(bǔ)全撕予。

3.3 對象的訪問定位

Java程序需要通過棧上的reference數(shù)據(jù)來操作堆上的具體對象

目前主流的訪問方式有使用句柄和直接指針兩種:

  • A. 若使用句柄訪問,Java堆中將會劃分出一塊內(nèi)存來作為句柄池蜈首,reference中存儲的就是對象的句柄地址实抡,而句柄中包含了對象實例數(shù)據(jù)與類型數(shù)據(jù)各自的具體地址信息。
通過句柄訪問對象
  • B. 若使用直接指針訪問欢策,Java堆對象的布局中就必須考慮如何放置訪問類型數(shù)據(jù)的相關(guān)信息吆寨,而reference中存儲的直接就是對象地址。
通過直接指針訪問對象
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末踩寇,一起剝皮案震驚了整個濱河市啄清,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌俺孙,老刑警劉巖辣卒,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掷贾,死亡現(xiàn)場離奇詭異,居然都是意外死亡荣茫,警方通過查閱死者的電腦和手機(jī)想帅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來啡莉,“玉大人港准,你說我怎么就攤上這事∵中溃” “怎么了浅缸?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長魄咕。 經(jīng)常有香客問我疗杉,道長,這世上最難降的妖魔是什么蚕礼? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任烟具,我火速辦了婚禮,結(jié)果婚禮上奠蹬,老公的妹妹穿的比我還像新娘朝聋。我一直安慰自己,他們只是感情好囤躁,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布冀痕。 她就那樣靜靜地躺著,像睡著了一般狸演。 火紅的嫁衣襯著肌膚如雪言蛇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天宵距,我揣著相機(jī)與錄音腊尚,去河邊找鬼。 笑死满哪,一個胖子當(dāng)著我的面吹牛婿斥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播哨鸭,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼民宿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了像鸡?” 一聲冷哼從身側(cè)響起活鹰,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后志群,有當(dāng)?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
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留颈畸,地道東北人前塔。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像承冰,于是被迫代替她去往敵國和親华弓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

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