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

  1. Java虛擬機在執(zhí)行Java程序的過程中,會把它所管理的內(nèi)存劃分為若干個不同的數(shù)據(jù)區(qū)域。這些區(qū)域有各自不同的用途,以及創(chuàng)建和銷毀的時間秋秤,有些區(qū)域隨著虛擬機進程的啟動而存在,有些區(qū)域則依賴用戶線程的啟動和結(jié)束而建立和銷毀脚翘。根據(jù)Java SE 7虛擬機規(guī)范灼卢,Java虛擬機所管理的內(nèi)存將會包括以下幾個運行時數(shù)據(jù)區(qū)域:


    JVM 內(nèi)存數(shù)據(jù)區(qū)域
  • 程序計數(shù)器
    是一塊較小的內(nèi)存空間,可以看作是當前線程所執(zhí)行的字節(jié)碼的行號指示器来农,字節(jié)碼解釋器工作時就是改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令鞋真,分支、循環(huán)沃于、跳轉(zhuǎn)涩咖、異常處理、線程恢復等基礎功能都需要依賴這個計數(shù)器來完成繁莹。
    由于多線程是通過線程輪流切換并分配處理器執(zhí)行時間的方式來實現(xiàn)的檩互,所以為了線程切換后恢復到正確的執(zhí)行位置,每條線程都需要一個獨立的程序計數(shù)器咨演,各條線程計數(shù)器互不影響闸昨,因此這塊內(nèi)存屬于“線程私有”的。
    另外,如果線程正在執(zhí)行的是一個Java方法饵较,則計數(shù)器里記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址拍嵌;如果執(zhí)行的是Native方法,則計數(shù)器的值為空(Undefined)循诉。此內(nèi)存區(qū)域是Java虛擬機規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域横辆。
  • 虛擬機棧
    與程序計數(shù)器一樣,虛擬機棧也屬于線程私有的茄猫。虛擬機棧描述的是Java方法執(zhí)行時的內(nèi)存模型:每個方法在執(zhí)行的同時會創(chuàng)建一個棧幀(Stack frame)用于存儲局部變量表狈蚤、操作數(shù)棧、動態(tài)鏈接划纽、方法出口等信息脆侮。每一個方法從調(diào)用直至執(zhí)行完成的過程,就對應著一個棧幀在虛擬機中從入棧到出棧的過程阿浓。
    局部變量表存放了編譯器可知的各種基本數(shù)據(jù)類型(8種)、引用類型(可能指向?qū)ο笃鹗嫉刂诽U溃部赡苤赶蛞粋€代表對象的句柄或其他與此對象相關的位置)和returnAddress類型(指向了一條字節(jié)碼指令的地址)芭毙。
    64位長度的long和double會占用兩個局部變量空間,其余數(shù)據(jù)類型只占用一個卸耘。局部變量表所需的內(nèi)存空間在編譯器完成分配退敦,在方法運行時,局部變量表在幀中所占空間是確定的并且不會發(fā)生改變的蚣抗。
    虛擬機棧中可能會發(fā)生兩種異常情況:如果線程請求的棧深度大于虛擬機所允許的深度侈百,將拋出StackOverflowError異常;如果虛擬機椇舱。可以動態(tài)擴展钝域,如果擴展時無法申請到足夠的內(nèi)存,就會拋出OutOfMemoryError異常锭魔。
  • 本地方法棧
    與虛擬機棧所發(fā)揮的作用是非常相似的例证。虛擬機棧為Java方法(字節(jié)碼)服務,本地方法棧為虛擬機使用到的Native方法服務迷捧。也會拋出兩種異常织咧。

  • Java虛擬機管理內(nèi)存中最大的一塊。線程共享漠秋,在虛擬機啟動時創(chuàng)建笙蒙。用于存放對象實例,這一點在Java虛擬機規(guī)范中描述是:所有對象的實例以及數(shù)組都要在堆上分配庆锦,但是隨著技術的發(fā)展捅位,所有對象都在堆上分配也變得不是那么“絕對”了。如果在堆中沒有內(nèi)存完成實例分配,并且堆也無法再擴展時绿渣,將會拋出OutOfMemoryError異常朝群。
  • 方法區(qū)
    屬于線程共享的區(qū)域。用于存放被虛擬機加載的類信息中符、常量姜胖、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)淀散。當方法區(qū)無法滿足內(nèi)存分配需求時右莱,將會拋出OutOfMemoryError異常。
  1. 運行時常量池档插。
  • 是方法區(qū)的一部分慢蜓,Class文件中除了有類的版本、字段郭膛、方法晨抡、接口等描述信息外,還有一項信息是常量池(Constant Pool Table)则剃,用于存放編譯期生成的各種字面量和符號引用耘柱,這部分內(nèi)容將在類加載后進入方法區(qū)的運行時常量池中存放。
  • 運行時常量池具有動態(tài)性棍现。Java語言并不要求常量一定只有編譯器才能產(chǎn)生调煎,運行期間也可以將新的常量放入池中,這種特性被開發(fā)人員利用的比較多的是String類的intern()方法己肮。
  • 常量池屬于方法區(qū)的一部分士袄,也會拋出OutOfMemoryError異常。
  1. 直接內(nèi)存
  • 并不是虛擬機運行時數(shù)據(jù)區(qū)的一部分谎僻,也不是Java虛擬機規(guī)范中定義的內(nèi)存區(qū)域娄柳,但是這部分內(nèi)存也被頻繁的使用,也有可能導致OutOfMemoryError異常艘绍。
  • 在JDK1.4中新加入了NIO(New Input/Output)類西土,引入了一種基于通道和緩沖區(qū)的I/O方式,它可以使用Native函數(shù)庫直接分配堆外內(nèi)存鞍盗,然后通過一個存儲在Java堆中的DirectByteBuffer對象作為這塊內(nèi)存的引用進行操作需了。這樣能在一些場景中顯著提高性能,因為避免了在Java堆中和Native堆中來回復制數(shù)據(jù)般甲。
  1. 對象的創(chuàng)建(討論普通的對象肋乍,不包括數(shù)組和Class對象等)
  • 虛擬機遇到new指令時,首先檢查這個指令的參數(shù)能否在常量池定位到一個類的符號引用敷存,并且檢查這個符號引用代表的類是否已經(jīng)被加載墓造、解析和初始化堪伍。如果沒有,就必須先執(zhí)行相應的類的加載過程觅闽。
  • 類加載檢查通過后帝雇,虛擬機就會分配內(nèi)存。對象所需的內(nèi)存大小在類加載完成后便可以完全確定蛉拙,而分配內(nèi)存有兩種策略:第一個是“指針碰撞”尸闸,要求Java堆的內(nèi)存是規(guī)整的,測試只需要移動邊界指針即可孕锄;第二種情況是Java堆的內(nèi)存不是規(guī)整的吮廉,已使用的內(nèi)存和被占用的內(nèi)存相互交錯,這種情況下需要根據(jù)Java虛擬機維護的空閑列表(Free list)來找到一塊足夠大的空間分配給對象實例畸肆。而這兩種方式又與垃圾回收策略密切相關宦芦。
  • 考慮在并發(fā)條件下對象創(chuàng)建并不是線程安全的。有兩種方案:一種是對分配內(nèi)存空間的動作進行同步處理轴脐;另一種是把內(nèi)存分配的動作按照線程劃分在不同的空間之中進行调卑,即TLAB
  • 虛擬機將分配到的內(nèi)存空間都初始化為零值(不包括對象頭)
  • 接下來,虛擬機要對對象進行必要的設置大咱,例如這個對象時哪個類的實例恬涧、如何找到類的元數(shù)據(jù)信息、對象的哈希碼徽级、對象的GC分代年齡等信息气破。這些信息存放在對象頭之中聊浅。
  1. 對象的內(nèi)存布局(對象頭餐抢、實例數(shù)據(jù)和對齊填充)
  • HotSpot虛擬機對象頭有兩部分信息。第一部分用于存儲對象自身的運行時數(shù)據(jù)低匙,如哈希碼旷痕,GC分代年齡等;對象頭的另一部分是類型指針顽冶,即對象指向它類元數(shù)據(jù)的指針欺抗,虛擬機通過這個指針來確定這個對象是哪個類的實例。然而强重,并不是所有虛擬機都必須在對象數(shù)據(jù)上保留這個類型指針绞呈,換句話說,查找對象的類型并不一定需要通過對象本身间景,有的虛擬機通過句柄來實現(xiàn)佃声。對象頭在32位和64位虛擬機中分別是32bit和64bit。
  • 實例數(shù)據(jù)部分是對象真正存儲的有效信息倘要。無論是從父類繼承的圾亏,還是子類本身的,都需要記錄起來。字段的存儲順序會受到虛擬機分配策略參數(shù)和字段在Java源碼中定義順序的影響志鹃。HotSpot虛擬機默認的分配策略是longs/doubles夭问,ints,shorts/chars曹铃,bytes/booleans缰趋,oops(引用),相同長度的字段總被分配到一起铛只。在滿足這個前提條件下埠胖,父類中出現(xiàn)的變量會出現(xiàn)在子類變量之前。如果CompactFields參數(shù)值為true淳玩,那么子類中較窄的變量也可能會插入到父類變量的空隙之中直撤。
  • 對齊填充部分并不是必然存在的。HotSpot要求對象的起始地址必須是8字節(jié)的整數(shù)倍蜕着,換句話說谋竖,對象的大小必須是8字節(jié)的整數(shù)倍。而對象頭正好是8的整數(shù)倍承匣,因此蓖乘,當對象實例數(shù)據(jù)部分沒有對齊時,就需要通過對齊填充來補全韧骗。
  1. 對象的訪問定位(使用句柄和直接指針兩種)
  • 使用句柄訪問嘉抒。Java堆中會劃分出一塊內(nèi)存作為句柄池,reference中存儲的是對象的句柄地址袍暴。


    通過句柄訪問對象
  • 通過指針訪問些侍。


    通過指針訪問對象
  • 這兩種訪問定位方式各有優(yōu)勢。使用句柄的好處是reference中存儲的是穩(wěn)定的句柄地址政模,在對象被移動(垃圾收集是對象移動是非常普遍的行為)是reference本身不需要修改岗宣。
    使用指針訪問的最大好處是速度很快,它節(jié)省了一次指針定位的時間開銷淋样,由于對象的訪問十分頻繁耗式,因此這類開銷積少成多會十分可觀。就HotSpot而言趁猴,它是使用指針直接訪問對象刊咳。
  1. Java堆溢出
  • Java堆用于存儲對象實例,只要不斷的創(chuàng)建對象儡司,并且保證GC Roots到對象之間有可達路徑來避免垃圾回收機制清除對象娱挨,那么對象數(shù)量達到最大堆的容量限制后就會產(chǎn)生OutOfMemoryError異常。


    Java 堆溢出實例
  • 當發(fā)生這個異常后枫慷,一般的手段是先通過內(nèi)存映像分析工具(如Eclipse Memory Analyzer)對Dump出來的堆存儲快照進行分析让蕾。重點是分析是出現(xiàn)了內(nèi)存泄漏還是內(nèi)存溢出浪规。
    如果是內(nèi)存泄漏,可進一步通過工具查看泄漏對象到GC Roots的引用鏈探孝。于是就能找到泄漏對象是通過怎樣的路徑與GC Roots相關聯(lián)而導致無法回收孩锡,這樣可以準確的定位出泄漏代碼的位置呻澜。
    如果是內(nèi)存溢出,就應該檢查虛擬機的堆參數(shù)(-Xmx與-Xms),與機器物理內(nèi)存對比看是否還可以調(diào)大狸窘。

  1. 虛擬機棧和本地方法棧溢出
  • 在單個線程下脯丝,無論是由于棧幀太大還是虛擬機棧容量太小脓钾,當無法分配內(nèi)存的時候尼夺,虛擬機拋出的都是StackOverflowError異常,這樣通常發(fā)生在無限遞歸調(diào)用之中绍些。
  • 如果測試不限于單線程捞慌,通過不斷的建立線程的方式倒是可以產(chǎn)生OutOfMemoryError異常。原因是這樣:操作系統(tǒng)分給每個進程的內(nèi)存是有限的柬批,這個有限的內(nèi)存減去堆容量啸澡、方法區(qū)容量和程序計數(shù)器容量(可以忽略不計),剩下的就是由所有線程的虛擬機棧和本地方法棧瓜分氮帐。創(chuàng)建的線程多到一定數(shù)量時嗅虏,這塊內(nèi)存就被完全占用,此時就會拋出異常上沐。


    由過多線程引起的棧OutOfMemoryError異常

    結(jié)果
  • 如果在開發(fā)過程中皮服,線程的數(shù)量不能被減少,就只能通過減小堆容量和減小棧容量來換取更多的線程参咙,以此達到更高的效率龄广。
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市昂勒,隨后出現(xiàn)的幾起案子蜀细,更是在濱河造成了極大的恐慌舟铜,老刑警劉巖戈盈,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異谆刨,居然都是意外死亡塘娶,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門痊夭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刁岸,“玉大人,你說我怎么就攤上這事她我『缡铮” “怎么了迫横?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長酝碳。 經(jīng)常有香客問我矾踱,道長,這世上最難降的妖魔是什么疏哗? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任呛讲,我火速辦了婚禮,結(jié)果婚禮上返奉,老公的妹妹穿的比我還像新娘贝搁。我一直安慰自己,他們只是感情好芽偏,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布雷逆。 她就那樣靜靜地躺著,像睡著了一般污尉。 火紅的嫁衣襯著肌膚如雪关面。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天十厢,我揣著相機與錄音等太,去河邊找鬼。 笑死蛮放,一個胖子當著我的面吹牛缩抡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播包颁,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼瞻想,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了娩嚼?” 一聲冷哼從身側(cè)響起蘑险,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎岳悟,沒想到半個月后佃迄,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贵少,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡滔灶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年麻车,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唆鸡。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡争占,死狀恐怖臂痕,靈堂內(nèi)的尸體忽然破棺而出猿涨,到底是詐尸還是另有隱情叛赚,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站事镣,受9級特大地震影響璃哟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜阳似,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望盛杰。 院中可真熱鬧即供,春花似錦于微、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伙单。三九已至吻育,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間摊趾,已是汗流浹背砾层。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留铸董,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓悲幅,卻偏偏與公主長得像站蝠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子留荔,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

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