Java記憶體區域與記憶體溢出異常
本文内容來源于深入了解JAVA虛拟機一書,為友善記憶做的一些整理:
- 運作時資料區域
- HotSpot虛拟機對象
- OOM異常
運作時資料區域
- 程式計數器
- Java虛拟機棧
- 本地方法棧
- Java堆
- 方法區
程式計數器
占用很小的記憶體空間,可以看作是目前線程所執行的位元組碼的行号訓示器。
一個核心在一個确定的時刻隻會執行一條線程中的指令,每個線程都有一個獨立的程式計數器,它是線程私有的。
如果線程正在執行的是一個Java方法,該計數器記錄的是正在執行虛拟機位元組碼指令的位址;如果是Native方法,計數器值為空。
Java虛拟機的多線程是通過線程輪流切換并配置設定處理器執行時間的方式來實作的。
程式計數器不會出現OOM現象。
Java虛拟機棧
生命周期與線程相同,是線程私有的記憶體區域
每個方法在執行的同時都會建立一個棧幀,用來存放局部變量表、操作數棧、動态連結、方法出口等資訊。一個方法的執行對應着該棧針在虛拟機棧中的入棧和出棧過程。
局部變量表存放基本資料類型、對象引用、returnAddress類型。
如果線程請求的棧深度大于虛拟機所允許的深度,将會抛出StackOverflowError異常;對于可以動态擴充的虛拟機棧,當無法申請到記憶體時,抛出OOM異常。
本地方法棧
與Java虛拟機棧不同的是,它為Native方法服務
Java堆
所有線程共享的一塊記憶體區域,是Java虛拟機所管理的記憶體中最大的一塊,用來存放對象執行個體和數組。
記憶體回收角度:可以細分為新生代和老年代,更細分為Eden空間、From Survivor空間和To Survivor空間。
記憶體配置設定角度:可劃分為多個線程私有的配置設定緩沖區(TLAB)。
Java堆可以處于實體上不連續的記憶體空間中,隻要邏輯上連續即可。
方法區
所有線程共享的記憶體區域,存放常量、靜态變量、即時編譯器編譯後的代碼和已被虛拟機加載的類資訊等
該區域的回收主要是針對常量池的回收和對類型的解除安裝。
運作時常量池是方法區的一部分,主要用來存放Class檔案中描述的符号引用以及翻譯出來的直接引用。
類資訊包括類名、通路修飾符、常量池、字段描述、方法描述等。
補充
直接記憶體:非虛拟機運作時資料區域,JDK1.4中新加入NIO類,是一種基于通道與緩沖區的I/O方式,可以使用Native函數庫直接配置設定堆外記憶體。
Java堆中存儲的DirectByteBuffer對象作為這塊記憶體的引用。
HotSpot虛拟機對象
對象的建立
虛拟機遇到一條new指令時,首先去檢查這個指令的參數能否在常量池中定位到一個類的符号引用,并且檢查這個符号引用代表的類是否已被加載、解析和初始化,如果沒有就必須先執行相應的類加載機制。
為新生對象配置設定記憶體,配置設定方式有指針碰撞和空閑清單,具體配置設定方式由Java堆是否規整決定,而Java堆是否規整由所采用的垃圾收集器是否帶有壓縮整理的功能決定。
保證記憶體配置設定時的線程安全有兩種方法:一、采用CAS配上失敗重試,保證更新操作的原子性;二、在TLAB(本地線程配置設定緩沖)上進行配置設定。
将配置設定到的記憶體空間都初始化為0值,對對象進行必要的設定(哪個類的執行個體、對象的哈希碼、GC分代年齡等),将這些資訊存放在對象的對象頭之中。
執行Init()方法。
對象的記憶體布局
分3塊區域:對象頭、執行個體資料、對齊填充
對象頭包括兩部分:一、Mark Word,存儲對象自身的運作時資料,包括哈希碼、GC分代年齡、鎖狀态标志、線程持有的鎖、偏向線程ID、偏向時間戳等;二、類型指針,對象指向它的類中繼資料的指針,虛拟機通過這個指針來确定這個對象是哪個類的執行個體。
執行個體資料是對象真正存儲的有效資訊。HotSpot虛拟機的配置設定政策為相同寬度的字段總是被配置設定到一起。
對其填充并不一定存在,僅僅起占位符的作用,對象起始位址必須是8位元組的整數倍,即對象的大小必須是8位元組的整數倍,當執行個體資料沒有對齊時,就需要通過對其填充來補全。
對象的通路定位
通過句柄和直接指針兩種方式
句柄方式:Java堆中需要劃分一塊記憶體作為句柄池,references中存放的就是對象的句柄位址,句柄中包含了對象執行個體資料與類型資料各自的具體位址資訊,好處在于當對象被移動時,隻會改變句柄中的執行個體資料指針,而reference本身不需要修改。
直接指針方式:reference中存儲的直接是對象的位址,通路速度快。
OOM異常
Java堆溢出、虛拟機棧和本地方法棧溢出、方法區溢出和運作時常量池溢出、本機直接記憶體溢出