本文將介紹了 Java 虛擬機內(nèi)存的各個區(qū)域以及這些區(qū)域的作用、服務對象和其中可能出現(xiàn)的異常等。
JVM 運行時數(shù)據(jù)區(qū)域
Java 虛擬機在執(zhí)行 Java 程序的過程中會把它所管理的內(nèi)存劃分為五個不同的數(shù)據(jù)區(qū)域(如圖所示)梆奈。
- 紅色邊框的是由所有線程共享的數(shù)據(jù)區(qū)
- 藍色邊框的是線程隔離的數(shù)據(jù)區(qū)
除了程序計數(shù)器之外撵颊,其他四個區(qū)域都可能會出現(xiàn) OutOfMemoryError
異常。
程序計數(shù)器
程序計數(shù)器是一塊較小的內(nèi)存空間
是當前線程所執(zhí)行的字節(jié)碼的行號顯示器
字節(jié)碼解釋器就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令的
-
執(zhí)行 Java 方法和執(zhí)行 Native 方法的區(qū)別:
- 執(zhí)行 Java 方法時抱虐,計數(shù)器記錄虛擬機正在執(zhí)行的字節(jié)碼指令的地址
- 執(zhí)行 Native 方法時颈将,無記錄梢夯,也就是計數(shù)器的值為空(Undefined)
程序計數(shù)器是唯一一個不會出現(xiàn) OOM 的區(qū)域。
Java 虛擬機棧
每個 Java 方法被執(zhí)行的時候晴圾,Java 虛擬機都會同步創(chuàng)建一個棧幀用于存儲局部變量表颂砸、操作數(shù)棧、動態(tài)連接疑务、方法出口等信息
每個方法被調(diào)用一直到執(zhí)行完畢的過程沾凄,都對應著一個棧幀在 Java 虛擬機棧中從入棧到出棧的過程
Java 虛擬機棧服務于 Java 方法
-
可能出現(xiàn)的異常:
-
StackOverflowError
:線程請求的棧深度 大于 Java 虛擬機所允許的深度時 -
OutOfMemoryError
:在 Java 虛擬機棧容量可動態(tài)擴展的情況下,當棧擴展時無法申請到足夠的內(nèi)存時
-
虛擬機參數(shù)設置:
-Xss
本地方法棧
與 Java 虛擬機棧發(fā)揮的作用類似知允,區(qū)別在于本地方法棧服務于 Native 方法。
可能出現(xiàn)的異常:與 Java 虛擬機棧一樣叙谨。
Java 堆
唯一目的:存放對象實例
垃圾回收器管理的內(nèi)存區(qū)域
可以處于物理上不連續(xù)的內(nèi)存空間中温鸽,但是在邏輯上應該是連續(xù)的
-
可能出現(xiàn)的異常:
-
OutOfMemoryError
:Java 堆中沒有內(nèi)存完成實例分配,并且堆也無法再擴展時
-
-
虛擬機參數(shù)設置:
最大值:
-Xmx
最小值:
-Xms
兩個參數(shù)設置成相同值時可避免堆自動擴展
方法區(qū)
用于存儲已被 Java 虛擬機加載的類型信息、常量涤垫、靜態(tài)變量姑尺、即時編譯器編譯后的代碼緩存等數(shù)據(jù)
可以選擇不實現(xiàn)垃圾收集,換句話說垃圾收集行為在這個區(qū)域非常少見蝠猬,但是對于經(jīng)常動態(tài)性生成大量 Class 的應用切蟋,如 Spring 等,需要特別注意類的回收情況
-
可能出現(xiàn)的異常:
-
OutOfMemoryError
:方法區(qū)無法滿足新的內(nèi)存分配需求時
-
運行時常量池
- 運行時常量池是方法區(qū)的一部分
- Class 文件除了有類的版本榆芦、字段柄粹、方法、接口等描述信息外匆绣,還有一項是常量池表驻右,用于存放編譯期生成的各種字面量(就是代碼中定義的 static final 常量)和符號引用,這部分內(nèi)容將在類加載后存放到方法區(qū)的運行時常量池中
- 可能出現(xiàn)的異常:
-
OutOfMemoryError
:常量池無法再申請到內(nèi)存時
-
直接內(nèi)存
不屬于 Java 虛擬機運行時數(shù)據(jù)區(qū)域的一部分崎淳,放到這里講是因為這部分內(nèi)存在使用的時候也可能導致
OutOfMemoryError
異常的出現(xiàn):各個內(nèi)存區(qū)域總和大于物理內(nèi)存限制(包括物理的和操作系統(tǒng)級的限制)堪夭,從而導致動態(tài)擴展時出現(xiàn)OutOfMemoryError
異常JDK1.4 的 NIO 類可以使用 Native 函數(shù)庫直接分配堆外內(nèi)存,然后通過一個存儲在 Java 堆里面的 DirectByteBuffer 對象作為這塊內(nèi)存的引用進行操作拣凹,好處是避免了在 Java 堆和 Native 堆中來回復制數(shù)據(jù)森爽,能在一些場景中提高性能
-
虛擬機參數(shù)設置:
-XX:MaxDirectMemorySize
- 默認等于 Java 堆最大值,即
-Xmx
指定的值
- 默認等于 Java 堆最大值,即
HotSpot 虛擬機堆中的對象
介紹完 Java 虛擬機的運行時數(shù)據(jù)區(qū)域后嚣镜,我們大致了解的 Java 虛擬機內(nèi)存模型的概況拗秘。這一小節(jié)將介紹 HotSpot 虛擬機在 Java 堆中對象分配、布局和訪問的全過程祈惶。
對象的創(chuàng)建(new)
當虛擬機遇到 new 指令時:
- 檢查這個指令的參數(shù)是否能在常量池中定位到一個類的符號引用雕旨,并檢查這個符號引用代表的類是否已經(jīng)被加載、解析和初始化過捧请。如果沒有凡涩,則先把這個類加載進內(nèi)存
- 類加載檢查通過后,虛擬機將為這個新的對象分配內(nèi)存疹蛉,內(nèi)存的大小在類加載完成后確定
- 在 Java 堆中為新對象分配可用內(nèi)存
- 內(nèi)存分配完成后活箕,虛擬機將分配到的內(nèi)存空間都初始化為零值
- 虛擬機設置對象頭中的數(shù)據(jù)
- 此時,從虛擬機的角度看可款,對象已經(jīng)創(chuàng)建好了育韩,但從 Java 程序角度看,對象創(chuàng)建才剛剛開始闺鲸,構造函數(shù)還沒有執(zhí)行
第 3 步中筋讨,為對象分配可用內(nèi)存時,會涉及兩個問題:
1. 內(nèi)存分配方式
- 指針碰撞(Java 堆中的內(nèi)存是絕對規(guī)整的)
- 所有被使用過的內(nèi)存放在一邊摸恍,沒有被使用過的內(nèi)存放在另一邊悉罕,中間放一個指針赤屋,作為分界點的指示器,那所分配的內(nèi)存就僅僅是把那個指針向空閑內(nèi)存空間方向挪一段與對象大小相等的距離
- 空閑列表(Java 堆中被使用的內(nèi)存和空閑內(nèi)存相互交錯在一起)
- Java 虛擬機需要維護一個列表壁袄,記錄上哪些內(nèi)存塊是可用的类早,在分配的時候從列表中找一塊大小足夠大的空間劃分給對象實例
2. 在并發(fā)情況下虛擬機創(chuàng)建對象也并不是線程安全的,可能出現(xiàn)正在給對象 A 分配內(nèi)存嗜逻,指針還沒來得及修改涩僻,對象 B 又同時使用了原來的指針來分配內(nèi)存的情況
- 對分配內(nèi)存空間的動作進行同步處理( 采用 CAS 配上失敗重試的方式保證更新操作的原子性 )
- 把內(nèi)存分配的動作按照線程劃分在不同的空間中進行(每個線程在 Java 堆中預先分配一塊小內(nèi)存(這個小內(nèi)存稱為**本地線程分配緩沖區(qū)**),哪個線程要分配內(nèi)存栈顷,就在哪個線程的本地線程分配緩沖區(qū)中分配逆日,只有這個本地線程分配緩沖區(qū)分配完了,分配新的本地線程分配緩沖區(qū)才需要同步鎖定)
- 虛擬機參數(shù)設置:`-XX:+/-UseTLAB`
對象的內(nèi)存布局
在 HotSpot 虛擬機中妨蛹,對象在堆內(nèi)存中的存儲布局分為三部分:
- 對象頭(包括兩類信息)
- 用于存儲對象自身的運行時數(shù)據(jù)屏富,如 HashCode、GC 分代年齡蛙卤、鎖狀態(tài)標志狠半、線程持有的鎖、偏向線程 ID颤难、偏向時間戳等
- 類型指針神年,即對象指向它的類型元數(shù)據(jù)的指針,虛擬機通過這個指針來確定該對象是哪個類的實例行嗤。
- 實例數(shù)據(jù)(存儲我們在程序代碼中定義的各種類型的字段內(nèi)容)
- 這部分數(shù)據(jù)受到虛擬機分配策略參數(shù)(
-XX:FieldsAllocationStyle
)和字段在代碼中定義順序的影響
- 這部分數(shù)據(jù)受到虛擬機分配策略參數(shù)(
- 對齊填充(沒有實際意義已日,起到占位符的作用)
對象的訪問定位
我們創(chuàng)建對象之后自然是要使用對象,Java 程序會通過棧上的 reference 數(shù)據(jù)來操作堆上的具體對象(reference 是一個指向對象的引用)栅屏。
主流的對象訪問方式有兩種:
- 通過句柄訪問(reference 中存儲的是穩(wěn)定的句柄地址飘千,在對象被移動的時候只會改變句柄中的實例數(shù)據(jù)指針,而 reference 本身不需要被修改)
- 通過直接指針訪問(速度快得一批栈雳,節(jié)省了一次指針定位的時間開銷)
本文小結
本文從概念上介紹了 Java 虛擬機內(nèi)存的各個區(qū)域以及這些區(qū)域的作用护奈、服務對象和其中可能出現(xiàn)的異常等,還介紹了虛擬機創(chuàng)建對象(new)的過程哥纫、對象的內(nèi)存布局和如何訪問對象霉旗。