運行時數據區域
- 分爲線程共享和線程隔離的區域。
程序計數器
- 可看做當前線程所執行的字節碼的行號指示器。
- 線程執行java方法時:虛擬機字節碼指令的地址。
- 線程執行native方法時:空(undefined)。
- 唯一沒有規定任何OutOfMemoryError的區域。
虛擬機棧
- 描述java方法執行的內存模型:每個方法執行時創建棧幀,存儲局部變量表、操作數棧、動態鏈接、方法出口。
- 局部變量表存儲基本數據類型、引用類型,其中64位的long和double佔用2個局部變量空間。
- 異常:
- StackOverflowError:線程請求的棧深度大於虛擬機所允許的深度。
- OutOfMemoryError:虛擬機棧擴展時無法申請到足夠的內存。
本地方法棧
- 類似java虛擬機棧,但是使用native方法服務。
- Sun的HotSpot 虛擬機將本地方法棧和虛擬機棧合併。
堆
- 存儲對象實例和數組(JIT編譯器的優化使得對象並不一定存儲在堆上)。
- OutOfMemoryError:堆中沒有內存完成實例的分配,並且堆無法擴展。
方法區
- 存儲被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯的代碼等。
- JIT:將熱點代碼編譯成平臺相關的機器碼。
- 內存回收的目標:常量池的回收、類型的卸載。
- OutOfMemoryError:方法區無法滿足內存分配需求。
運行時常量池
- 方法區的一部分,存放編譯期生成的的字面量和符號引用,或者運行期間產生的常量(String.intern()方法)。
直接內存
- 不是虛擬機運行時數據區的一部分,可以通過NIO分配和操作這一堆外內存。
- OutOfMemoryError:各個內存區域的總和大於物理內存的限制。
對象的創建、佈局、訪問
- 基於HotSpot虛擬機討論,普通java對象,不包括數組、Class對象。
創建
- 類加載檢查。檢查常量池中是否有對應的類的符號引用,並且這個類是否已被加載、解析、初始化。如沒有,執行類加載。
- 分配內存。內存規整時使用指針碰撞,內存不規整時使用空閒列表,兩個以上的對象同時分配內存時,需要注意線程安全,解決方法:
- 同步內存分配動作:CAS和失敗重試,保證更新的原子性。
- 每個線程預先分配本地線程分配緩衝(Thread Local Allocation Buffer, TLAB),在各自的TLAB上分配內存,只有分配新的TLAB時,才需要同步鎖定。
- 初始化零值。除對象頭之外的內存空間初始化,保證對象的實例字段不附初始值就可以使用。也可以在TLAB分配時進行。
- 設置對象頭。包括屬於的類、如何找到類的元數據、對象哈希碼、對象GC分代年齡、偏向鎖等。
- 執行init方法。按照程序員意願初始化。
佈局
- 對象頭。
- 對象自身運行時數據。哈希碼、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時間戳等。
- 類型指針。指向類元數據,數組還需要包含數組長度。
- 實例數據。定義的各種類型字段,存儲順序受虛擬機分配策略和源碼中的定義順序影響,hotspot中相同寬度的字段分配到一起,父類中的變量在子類之前。
- 對齊填充。HotSpot內存管理系統要求對象的大小必須是8字節的整數倍。
訪問
- 句柄訪問。劃分句柄池和實例池,reference存儲對象句柄地址,句柄包含對象實例數據和類型數據的地址。
- 直接指針訪問。reference存儲對象地址,包含指向類型數據的指針。
- 句柄訪問的優勢:對象移動時只會改變句柄中的實例數據指針,reference本身不變。
- 直接指針訪問優勢:節省一次指針定位開銷,速度更快。HotSpot使用直接指針訪問。
OutOfMemoryError異常
- 堆溢出:不停創建對象,並維持 GC Roots 到對象之間的引用避免對象被回收。
- 棧溢出:
- 遞歸過深。
- 創建過多線程。這是由於棧的空間被每個線程瓜分。
- 方法區和運行時常量池溢出:
- String.intern()方法,在常量池中記錄首次出現的實例的引用。
- cglib產生大量的動態類。
- 本機直接內存溢出:Unsafe實例分配過多內存。
- 溢出實例。