Chapter2 Java內存區域與內存溢出異常

前言:剛開始接觸Java虛擬機的知識,參考的是周志明的《深入理解Java虛擬機》這本書。一方面整理思路,同時也為了方便以后查閱,所以整理了書中的內容。

? ? ? 本章首先介紹Java虛擬機的運行時數據區域,分為6個區域,主要從各個區域的作用、是否為線程共享、可能出現的異常進行描述。然后介紹了對象的創建過程、對象的內存布局以及如何訪問對象,在對象的創建過程中要注意內存分配的兩種方式(指針碰撞和空閑列表),在并發情況下如何做到線程安全(同步或者使用TLAB);對象的內存布局包括對象頭,實例數據和對齊填充三個部分;對象的訪問有兩種方式,使用句柄訪問或者使用直接指針訪問。最后,是實戰部分,模擬了Java堆溢出、棧溢出和方法區與運行時常量池的溢出,并簡要介紹了遇到這些情況如何分析和解決。

一.運行時數據區域

Java虛擬機所管理的內存包括以下6個運行時數據區域,有的區域隨著虛擬機進程的啟動而存在,有些區域則依賴用戶線程的啟動和結束而建立和銷毀

1.程序計數器

1)如果線程正在執行的是一個Java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;?如果正在執行的是Native方法,這個計數器的值為空

2)線程私有

3)唯一一個在Java虛擬機規范中沒有規定任何OutOfMemoryError情況的區域

2.Java虛擬機棧

1)描述的是Java方法執行的內存模型,每個方法在執行的同時都會創建一個棧幀,用于存儲局部變量表、操作數棧、動態連接、方法的出口信息等

每一個方法從調用直至執行完成的過程,就對應著一個棧幀在虛擬機棧中入棧到出棧的過程

其中,局部變量表存放了編譯期可知的各種基本數據類型、對象引用類型和returnAddress類型(指向了一條字節碼指令的地址);所需的內存空間在編譯期間完成分配

2)線程私有

3)規定了兩種異常狀況:

如果線程請求的深度大于虛擬機所允許的深度,將拋出StackOverflowError異常

如果虛擬機棧可以動態擴展,而擴展時無法申請到足夠的內存,就會拋出OutOfMemoryError異常

3.本地方法棧

1)本地方法棧為虛擬機使用到的Native方法服務;虛擬機棧為虛擬機執行Java方法(也就是字節碼)服務

Sun HotSpot虛擬機直接把虛擬機棧和本地方法棧合二為一

2)線程私有

3)規定了兩種異常狀況:StackOverflowError異常OutOfMemoryError異常

4.Java堆

1)此內存唯一的目的是存放對象實例,幾乎所有的對象實例都在這里分配內存(不是所有的對象實例,因為隨著JIT編譯器的發展與逃逸分析計數逐漸成熟,棧上分配、標量替換優化技術將會導致一些微妙的變化)

2)線程共享,在虛擬機啟動時創建

3)如果在堆中沒有內存完成實例分配,并且堆也無法再擴展時,將會拋出OutOfMemoryError異常

4)Java堆是Java虛擬機所管理的內存中最大的一塊

Java堆是垃圾收集器管理的主要區域,因此也被稱作GC堆(Garbage Collection Heap)

5.方法區

1)存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據

2)線程共享

3)當方法區無法滿足內存分配需求時,將拋出OutOfMemoryError異常

4)這個區域的內存回收的目標主要是針對常量池的回收和對類型的卸載

5)對于HotSpot虛擬機,很多人把方法區稱為“永久代”,本質上兩者不等價,僅僅因為它的設計團隊選擇把GC分代收集擴展至方法區,或者說使用永久代來實現方法區,這樣HotSpot的垃圾收集器可以像管理Java堆一樣管理這部分內存,省去專門為方法區編寫內存管理代碼的工作。但是,在JDK1.7中,已經把原本放在永久代的字符串常量池移出

6.運行時常量池

1)它是方法區的一部分

Class文件中有一項是常量池,用于存放編譯器生成的各種字面量和符號引用,這部分內容將在類加載后進入方法區的運行時常量池中存放

常量池中主要存放兩大類常量:字面量和符號引用。字面量比較接近于Java語言層面的常量的概念(如文本字符串、聲明為final的常量值等);

符號引用則屬于編譯原理方面的概念,包括了三類常量:類和接口的全限定名、字段的名稱和描述符、方法的名稱和描述符

2)線程共享

3)當常量池無法再申請到內存時會拋出OutOfMemoryError異常

4)并非預置入Class文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,如String類的intern()方法

二.HotSpot虛擬機對象探秘

1.對象的創建

1)虛擬機遇到一條new指令時,首先將去檢查new指令的參數是否能在常量池中定位到一個類的符號引用,并且檢查這個符號引用代表的類是否已經被加載、解析和初始化過。如果沒有,那么必須先執行相應的類加載過程

2)在類加載檢查通過后,接下來虛擬機將為新生對象分配內存

對象所需內存的大小在類加載完成后便可完全確定,為對象分配空間的任務等同于把一塊確定大小的內存從Java堆中劃分出來。

i)根據Java堆是否規整,有兩種分配方式:

指針碰撞(Bump the Pointer),假設Java堆中內存是絕對規整的,所有用過的內存都放在一邊,空閑的內存放在另一邊,中間放著一個指針作為分界點的指示器,那分配內存就僅僅是把那個指針向空閑空間那邊挪動一段與對象大小相等的距離,這種分配方式稱為“指針碰撞”。

空閑列表(Free List),假設Java堆中的內存不是規整的,已使用的內存和空閑的內存相互交錯,虛擬機就必須維護一個列表,記錄上哪些內存塊是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,并更新列表上的記錄,這種分配方式稱為“空閑列表”。

而Java堆是否規整又由所采用的垃圾收集器是否帶有壓縮整理功能決定(如,Serial、ParNew等帶整理過程,系統采用的分配算法是指針碰撞;CMS基于標記-清除,通常采用空閑列表)

ii)并發情況下的分配

對象創建在虛擬機中是非常頻繁的行為,即使僅僅修改一個指針所指向的位置,在并發情況下也并不是線程安全的,解決這個問題有兩個方案:

對分配內存空間的動作進行同步處理(虛擬機采用CAS配上失敗重試的方式保證更新操作的原子性);

把內存分配的動作按照線程劃分在不同的空間之中進行,即每個線程在Java堆中預先分配一小塊內存,稱為本地線程分配緩存(Thread Local Allocation Buffer,TLAB)。哪個線程要分配內存,就在哪個線程的TLAB上分配,只有TLAB用完并分配新的TLAB時,才需要同步鎖定。使用參數

-XX:+/-UseTLAB來設定虛擬機是否使用TLAB

3)內存分配完成后,虛擬機需要將分配到的內存空間都初始化為零值(不包括對象頭)

如果使用TLAB,這一工作過程可以提前至TLAB分配時進行。

此操作保證實例字段在Java中不賦初值就可以直接使用

4)虛擬機對對象進行必要的設置,如這個對象是哪個類的實例、如何才能找到類的元數據信息、對象的哈希碼值、對象的GC分代年齡等信息。這些信息存放在對象的對象頭之中

5)此時,從虛擬機的視角看,一個新的對象已經產生了,但從Java程序員的視角看,對象創建才剛剛開始,方法還沒執行,所有的字段都還為0.所以,執行new指令之后會接著執行init方法,把對象按照程序員的意愿進行初始化,這樣一個真正的對象才算完全生產出來

2.對象的內存布局

在HotSpot虛擬機中,對象的內存布局可以分為:對象頭、實例數據、對齊填充

1)對象頭包括兩部分信息:

第一部分用于存儲對象自身的運行時數據,如哈希碼、GC分代年齡、鎖標志狀態、線程持有的鎖、偏向線程ID、偏向時間戳等。這部分數據長度在32位和64位的虛擬機中分別是32bit和64bit ? 官方稱它為Mark Word

第二部分是類型指針,即對象指向它的類元素數據的指針,虛擬機通過指針來確定這個對象是哪個類的實例。(并不是所有的虛擬機都有類型指針)

如果對象是一個Java數組,在對象頭中還必須有一塊用于記錄數組長度的數據。

2)實例數據部分是對象真正存儲的有效信息,也是在程序代碼中所定義的各種類型的字段內容。

這部分的存儲順序會收到虛擬機分配策略(相同寬度的字段總是被分配到一起)參數和字段在Java源碼中的定義順序的影響。

3)對齊填充

起著占位符的作用(對象的大小必須是8字節的整數倍)

3.對象的訪問定位

1)通過棧上的reference數據來操作堆上的具體對象

使用直接指針訪問(HotSpot虛擬機),reference中存儲的直接就是對象地址。優點:速度快

2)使用句柄訪問,Java堆中會劃分出一塊內存來作為句柄池,reference中存儲的就是對象的句柄地址,句柄中包含了對象實例數據與類型數據各自的具體信息。

優勢:reference中存儲的是穩定的句柄地址,在對象被移動時只會改變句柄中的實例數據指針,而reference本身不需要修改


三.實戰部分:OutOfMemoryError異常

1.Java堆溢出

Java堆是用來存儲對象實例的,如果不斷地創建對象,并且通過GC Roots到對象之間有可達路徑來避免垃圾回收機制清除這些對象,那么在對象數量達到堆容量的上限時就會溢出異常。

1)如何模擬Java堆溢出呢?

設置JVM參數:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError

限制堆的內存為20M,在那么中通過死循環創建對象,就會出現OutOfMemoryError。

異常堆棧信息: java.lang.OutOfMemoryError: Java heap space

2)如何分析呢?

上面JVM中的第三個參數是讓虛擬機在出現內存溢出異常時Dump出當前的內存轉儲快照,可以通過內存映像分析工具(Eclipse Memory Analyzer)對Dump出來的轉儲快照進行分析,分析是出現了內存泄露(Memory Leak)還是內存溢出(Memory Overflow)。

如果是內存泄露,通過工具查看泄露對象到GC Roots的引用鏈,掌握泄露對象的類型信息及GC Roots引用鏈的信息,定位出泄露代碼的位置;

如果是內存溢出,即內存中的對象都必須存活著,那么檢查虛擬機的堆參數是否可以調大,并檢查代碼總是否存在某些對象生命周期過長、持有狀態時間過長的情況,減少程序運行期的內存消耗。

2.虛擬機棧和本地方法棧溢出

對于HotSpot虛擬機,棧容量由 -Xss參數設定,會出現兩種異常。

1)如何模擬棧的溢出呢?

在單線程下,使用-Xss減少棧的容量或是定義大量的本地變量,增大此方法幀中本地變量表的長度,都會拋出StackOverflowError。

在多線程情況下,通過不斷建立線程的方式可以產生內存溢出異常。出現這種異常后,如果是建立太多線程導致的內存溢出,而且又不能減少線程數或更換64位虛擬機,可以通過減少最大堆和減少棧容量來換取更多的線程(Xmx:最大堆容量+MaxPermSize:最大方法區容量+棧容量)。

3.方法區和運行時常量池溢出

在JDK1.6中,String.intern方法會把首次遇到的字符串實例復制到永久代中,返回的是永久代中這個字符串實例的引用;

在JDK1.7中,String.intern方法不再復制實例,只是在常量池中記錄首次出現的實例的引用。

方法區的溢出,基本思路是運行時產生大量的類去填滿方法區。

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

推薦閱讀更多精彩內容