JVM 在執(zhí)行 Java 程序的過程中會把它所管理的內(nèi)存劃分為如下幾個數(shù)據(jù)區(qū)域:
程序計數(shù)器
虛擬機棧
本地方法棧
堆
方法區(qū)
程序計數(shù)器
程序計數(shù)器是一塊很小的內(nèi)存空間,是當前線程所執(zhí)行字節(jié)碼的行號指示器,線程間私有不共享。執(zhí)行 Java 方法時計數(shù)器的值為字節(jié)碼指令的地址,執(zhí)行 Native 方法時值為空。不會出現(xiàn)OutOfMemoryError。
虛擬機棧
虛擬機棧為線程私有,生命周期與線程相同,描述 Java 方法執(zhí)行的內(nèi)存模型。方法執(zhí)行時創(chuàng)建棧幀,用于存儲局部變量表、操作數(shù)棧、方法出口等信息。
局部變量表存放了編譯器可知的基本數(shù)據(jù)類型、對象引用和 returnAddress類型(字節(jié)碼指令地址),所需內(nèi)存空間在編譯器完成分配,運行期間不改變大小。
線程請求棧深度大于虛擬機允許深度時拋出 StackOverflowError(大部分虛擬機可以動態(tài)擴容,但也允許固定長度的虛擬機棧),擴展時無法申請到足夠內(nèi)存拋出 OutOfMemoryError。
本地方法棧
本地方法棧為虛擬機執(zhí)行 Native 方法服務(wù),線程隔離。虛擬機規(guī)范中無強制規(guī)定,Sun HostSpot 將本地方法棧和虛擬機棧合二為一。能夠拋出 StackOverflowError 和 OutOfMemoryError 異常。
Java 堆
Java 堆是 JVM 所管理內(nèi)存中最大的一塊,所有線程共享,在虛擬機啟動時創(chuàng)建,用于存放對象實例,是垃圾回收的主要區(qū)域。
從內(nèi)存回收的角度可分為新生代和老年代,再細致劃分可分為 Eden 空間、From Survivor 空間 和 To Survivor 空間等。
Java 堆可以處于物理上不連續(xù)的內(nèi)存空間內(nèi),只要邏輯上連續(xù)即可。
在堆中沒有內(nèi)存完成實例分配也無法再擴展時拋出 OutOfMemoryError。
方法區(qū)
方法區(qū)由各個線程共享,用于存儲已被 JVM 加載的類信息、常量、靜態(tài)變量、即使編譯器編譯后的代碼等。
JVM 規(guī)范把方法區(qū)描述為堆的一個邏輯部分,別名 Non-Heap(目的是與 Java 堆區(qū)分開)。
JVM 規(guī)范對方法區(qū)的限制非常寬松,不需要連續(xù)內(nèi)存、固定大小或可擴展均可,還可以不實現(xiàn)垃圾回收。垃圾收集行為在這個區(qū)域較少出現(xiàn)。
方法區(qū)包含運行時常量池,Class 文件中的常量信息在類加載后存放于運行時常量池中。運行時常量池具備動態(tài)性,運行期間也可能將新的常量放入池中。
當方法區(qū)無法滿足內(nèi)存分配需求時會拋出 OutOfMemoryError。
直接內(nèi)存 *
直接內(nèi)存不是 JVM 運行時數(shù)據(jù)區(qū)的一部分,也不是 JVM 規(guī)范中定義的內(nèi)存區(qū)域,但這部分內(nèi)存會被頻繁使用,而且可能會導致 OutOfMemoryError 異常。
JDK 1.4 中新加入的NIO(New Input / Output)類引入了一種基于通道與緩沖區(qū)的 IO 方式,可以使用 Native 函數(shù)庫直接分配堆外內(nèi)存,再通過存儲在 Java 堆中的DirectByteBuffer 對象做為這塊內(nèi)存的引用進行操作。
直接內(nèi)存不受 Java 堆大小限制,但會受到本機總內(nèi)存及處理器尋址空間限制,動態(tài)擴展超出限制會拋出 OutOfMemoryError 異常。